org.sakaiproject.evaluation.tool.producers.EvaluationAssignProducer.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.evaluation.tool.producers.EvaluationAssignProducer.java

Source

/**
 * Copyright 2005 Sakai Foundation Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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.
 */
package org.sakaiproject.evaluation.tool.producers;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.sakaiproject.entitybroker.EntityReference;
import org.sakaiproject.evaluation.constant.EvalConstants;
import org.sakaiproject.evaluation.logic.EvalAuthoringService;
import org.sakaiproject.evaluation.logic.EvalCommonLogic;
import org.sakaiproject.evaluation.logic.EvalEvaluationService;
import org.sakaiproject.evaluation.logic.EvalSettings;
import org.sakaiproject.evaluation.logic.entity.AdhocGroupEntityProvider;
import org.sakaiproject.evaluation.logic.externals.ExternalHierarchyLogic;
import org.sakaiproject.evaluation.logic.model.EvalGroup;
import org.sakaiproject.evaluation.logic.model.EvalHierarchyNode;
import org.sakaiproject.evaluation.model.EvalAdhocGroup;
import org.sakaiproject.evaluation.model.EvalAssignGroup;
import org.sakaiproject.evaluation.model.EvalAssignUser;
import org.sakaiproject.evaluation.model.EvalEvaluation;
import org.sakaiproject.evaluation.tool.renderers.HierarchyTreeNodeSelectRenderer;
import org.sakaiproject.evaluation.tool.renderers.NavBarRenderer;
import org.sakaiproject.evaluation.tool.utils.RenderingUtils;
import org.sakaiproject.evaluation.tool.viewparams.AdhocGroupParams;
import org.sakaiproject.evaluation.tool.viewparams.EvalViewParameters;
import org.sakaiproject.evaluation.utils.ComparatorsUtils;

import uk.org.ponder.messageutil.MessageLocator;
import uk.org.ponder.rsf.components.UIBranchContainer;
import uk.org.ponder.rsf.components.UICommand;
import uk.org.ponder.rsf.components.UIContainer;
import uk.org.ponder.rsf.components.UIELBinding;
import uk.org.ponder.rsf.components.UIForm;
import uk.org.ponder.rsf.components.UIInitBlock;
import uk.org.ponder.rsf.components.UIInternalLink;
import uk.org.ponder.rsf.components.UILink;
import uk.org.ponder.rsf.components.UIMessage;
import uk.org.ponder.rsf.components.UIOutput;
import uk.org.ponder.rsf.components.UIOutputMany;
import uk.org.ponder.rsf.components.UISelect;
import uk.org.ponder.rsf.components.UISelectChoice;
import uk.org.ponder.rsf.components.decorators.UIDisabledDecorator;
import uk.org.ponder.rsf.components.decorators.UIFreeAttributeDecorator;
import uk.org.ponder.rsf.components.decorators.UILabelTargetDecorator;
import uk.org.ponder.rsf.components.decorators.UIStyleDecorator;
import uk.org.ponder.rsf.components.decorators.UITooltipDecorator;
import uk.org.ponder.rsf.flow.ARIResult;
import uk.org.ponder.rsf.flow.ActionResultInterceptor;
import uk.org.ponder.rsf.request.EarlyRequestParser;
import uk.org.ponder.rsf.view.ComponentChecker;
import uk.org.ponder.rsf.viewstate.SimpleViewParameters;
import uk.org.ponder.rsf.viewstate.ViewParameters;
import uk.org.ponder.rsf.viewstate.ViewParamsReporter;
import uk.org.ponder.rsf.viewstate.ViewStateHandler;

/**
 * View for assigning an evaluation to groups and hierarchy nodes. 
 * 
 * This Producer has instance variables for tracking state of the view, and
 * should never be reused or used as a singleton.
 * 
 * @author Aaron Zeckoski (aaronz@vt.edu)
 * @author Steve Githens (sgithens@caret.cam.ac.uk)
 */
@SuppressWarnings("deprecation")
public class EvaluationAssignProducer extends EvalCommonProducer
        implements ViewParamsReporter, ActionResultInterceptor {

    private static final Log LOG = LogFactory.getLog(EvaluationAssignProducer.class);

    public static final String VIEW_ID = "evaluation_assign";

    public String getViewID() {
        return VIEW_ID;
    }

    private EvalEvaluationService evaluationService;

    public void setEvaluationService(EvalEvaluationService evaluationService) {
        this.evaluationService = evaluationService;
    }

    private ExternalHierarchyLogic hierarchyLogic;

    public void setExternalHierarchyLogic(ExternalHierarchyLogic logic) {
        this.hierarchyLogic = logic;
    }

    private EvalCommonLogic commonLogic;

    public void setCommonLogic(EvalCommonLogic bean) {
        this.commonLogic = bean;
    }

    private Locale locale;

    public void setLocale(Locale locale) {
        this.locale = locale;
    }

    public MessageLocator messageLocator;

    public void setMessageLocator(MessageLocator messageLocator) {
        this.messageLocator = messageLocator;
    }

    private EvalSettings settings;

    public void setSettings(EvalSettings settings) {
        this.settings = settings;
    }

    private HierarchyTreeNodeSelectRenderer hierUtil;

    public void setHierarchyRenderUtil(HierarchyTreeNodeSelectRenderer util) {
        hierUtil = util;
    }

    private ViewStateHandler vsh;

    public void setViewStateHandler(ViewStateHandler vsh) {
        this.vsh = vsh;
    }

    private NavBarRenderer navBarRenderer;

    public void setNavBarRenderer(NavBarRenderer navBarRenderer) {
        this.navBarRenderer = navBarRenderer;
    }

    private EvalAuthoringService authoringService;

    public void setAuthoringService(EvalAuthoringService authoringService) {
        this.authoringService = authoringService;
    }

    private RenderingUtils renderingUtils;

    public void setRenderingUtils(RenderingUtils renderingUtils) {
        this.renderingUtils = renderingUtils;
    }

    public void fill(UIContainer tofill, ViewParameters viewparams, ComponentChecker checker) {

        // local variables used in the render logic
        String currentUserId = commonLogic.getCurrentUserId();
        DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
        DateFormat dtf = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, locale);

        navBarRenderer.makeNavBar(tofill, NavBarRenderer.NAV_ELEMENT, this.getViewID());

        EvalViewParameters evalViewParams = (EvalViewParameters) viewparams;
        if (evalViewParams.evaluationId == null) {
            throw new IllegalArgumentException("Cannot access this view unless the evaluationId is set");
        }

        /**
         * This is the evaluation we are working with on this page,
         * this should ONLY be read from, do not change any of these fields
         */
        EvalEvaluation evaluation = evaluationService.getEvaluationById(evalViewParams.evaluationId);

        //Are we using the selection options (UCT)? YES
        boolean useSelectionOptions = ((Boolean) settings.get(EvalSettings.ENABLE_INSTRUCTOR_ASSISTANT_SELECTION));

        //find out is this evaluation will contain any Instructor/TA questions based in it's template
        List<String> validItemCategories;
        boolean hasInstructorQuestions = true;
        boolean hasAssistantQuestions = true;
        if (useSelectionOptions) {
            validItemCategories = renderingUtils.extractCategoriesInTemplate(evaluation.getTemplate().getId());
            hasInstructorQuestions = validItemCategories.contains(EvalConstants.ITEM_CATEGORY_INSTRUCTOR);
            hasAssistantQuestions = validItemCategories.contains(EvalConstants.ITEM_CATEGORY_ASSISTANT);
        }

        LOG.debug("Template id: " + evaluation.getTemplate().getId() + " useSelectionOptions: "
                + useSelectionOptions + " hasInstructorQuestions: " + hasInstructorQuestions
                + " hasAssistantQuestions: " + hasAssistantQuestions);

        String actionBean = "setupEvalBean.";
        Boolean newEval = false;

        UIInternalLink.make(tofill, "eval-settings-link", UIMessage.make("evalsettings.page.title"),
                new EvalViewParameters(EvaluationSettingsProducer.VIEW_ID, evalViewParams.evaluationId));
        if (EvalConstants.EVALUATION_STATE_PARTIAL.equals(evaluation.getState())) {
            // creating a new eval
            UIMessage.make(tofill, "eval-start-text", "starteval.page.title");
            newEval = true;
        }

        UIMessage.make(tofill, "assign-eval-edit-page-title", "assigneval.assign.page.title",
                new Object[] { evaluation.getTitle() });
        UIMessage.make(tofill, "assign-eval-instructions", "assigneval.assign.instructions",
                new Object[] { evaluation.getTitle() });

        UIMessage.make(tofill, "evalAssignInstructions", "evaluationassignconfirm.eval.assign.instructions",
                new Object[] { df.format(evaluation.getStartDate()) });

        // display info about the evaluation (dates and what not)
        UIOutput.make(tofill, "startDate", dtf.format(evaluation.getStartDate()));

        if (evaluation.getDueDate() != null) {
            UIBranchContainer branch = UIBranchContainer.make(tofill, "showDueDate:");
            UIOutput.make(branch, "dueDate", dtf.format(evaluation.getDueDate()));
        }

        if (evaluation.getStopDate() != null) {
            UIBranchContainer branch = UIBranchContainer.make(tofill, "showStopDate:");
            UIOutput.make(branch, "stopDate", dtf.format(evaluation.getStopDate()));
        }

        if (evaluation.getViewDate() != null) {
            UIBranchContainer branch = UIBranchContainer.make(tofill, "showViewDate:");
            UIOutput.make(branch, "viewDate", dtf.format(evaluation.getViewDate()));
        }

        /* 
         * About this form.
         * 
         * This is a GET form that has 2 UISelects, one for Hierarchy Nodes, and
         * one for Eval Groups (which includes adhoc groups).  They are interspered
         * and mixed together. In order to do this easily we pass in empty String
         * arrays for the option values and labels in the UISelects. This is partially
         * because rendering each individual checkbox requires and integer indicating
         * it's position, and this view is too complicated to generate these arrays
         * ahead of time.  So we generate the String Arrays on the fly, using the list.size()-1
         * at each point to get this index.  Then at the very end we update the UISelect's
         * with the appropriate optionlist and optionnames. This actually works 
         * really good and the wizard feels much smoother than it did with the 
         * old session bean.
         * 
         * Also see the comments on HierarchyTreeNodeSelectRenderer. 
         * 
         */
        UIForm form = UIForm.make(tofill, "eval-assign-form");

        // Things for building the UISelect of Hierarchy Node Checkboxes
        List<String> hierNodesLabels = new ArrayList<>();
        List<String> hierNodesValues = new ArrayList<>();
        UISelect hierarchyNodesSelect = UISelect.makeMultiple(form, "hierarchyNodeSelectHolder", new String[] {},
                new String[] {}, (useSelectionOptions ? actionBean : "") + "selectedHierarchyNodeIDs",
                evalViewParams.selectedHierarchyNodeIDs);
        String hierNodesSelectID = hierarchyNodesSelect.getFullID();

        // Things for building the UISelect of Eval Group Checkboxes
        List<String> evalGroupsLabels = new ArrayList<>();
        List<String> evalGroupsValues = new ArrayList<>();
        UISelect evalGroupsSelect = UISelect.makeMultiple(form, "evalGroupSelectHolder", new String[] {},
                new String[] {}, (useSelectionOptions ? actionBean : "") + "selectedGroupIDs",
                evalViewParams.selectedGroupIDs == null ? new String[] {} : evalViewParams.selectedGroupIDs);
        String evalGroupsSelectID = evalGroupsSelect.getFullID();

        /*
         * About the 4 collapsable areas.
         * 
         * What's happening here is that we have 4 areas: hierarchy, groups, 
         * new adhoc groups, and existing adhoc groups that can be hidden and selected
         * which a checkbox for each one.
         * 
         */

        Boolean useAdHocGroups = (Boolean) settings.get(EvalSettings.ENABLE_ADHOC_GROUPS);
        Boolean showHierarchy = (Boolean) settings.get(EvalSettings.DISPLAY_HIERARCHY_OPTIONS);

        // NOTE: this is the one place where the perms should be used instead of user assignments (there are no assignments yet) -AZ

        // get the current eval group id (ie: reference site id) that the user is in now
        String currentEvalGroupId = commonLogic.getCurrentEvalGroup();
        // get the current eval group id (ie: site id) that the user is in now
        String currentSiteId = EntityReference.getIdFromRef(currentEvalGroupId);

        // get the groups that this user is allowed to assign evals to
        List<EvalGroup> assignEvalGroups;
        // for backwards compatibility we will pull the list of groups the user is being evaluated in as well and merge it in
        List<EvalGroup> beEvalGroups;

        Boolean isGroupFilterEnabled = (Boolean) settings.get(EvalSettings.ENABLE_FILTER_ASSIGNABLE_GROUPS);

        if (isGroupFilterEnabled) {
            assignEvalGroups = commonLogic.getFilteredEvalGroupsForUser(currentUserId,
                    EvalConstants.PERM_ASSIGN_EVALUATION, currentSiteId);
            beEvalGroups = commonLogic.getFilteredEvalGroupsForUser(currentUserId, EvalConstants.PERM_BE_EVALUATED,
                    currentSiteId);
        } else {
            assignEvalGroups = commonLogic.getEvalGroupsForUser(currentUserId,
                    EvalConstants.PERM_ASSIGN_EVALUATION);
            beEvalGroups = commonLogic.getEvalGroupsForUser(currentUserId, EvalConstants.PERM_BE_EVALUATED);
        }

        /*
         * EVALSYS-987- Side effect
         * remove ad hoc groups from view as they are handled already in another 
         * section in the same view. 
         */
        List<EvalGroup> evalGroups = new ArrayList<>();
        for (EvalGroup evalGroup : assignEvalGroups) {
            if (!evalGroups.contains(evalGroup) && !EvalConstants.GROUP_TYPE_ADHOC.equals(evalGroup.type)) {
                evalGroups.add(evalGroup);
            }
        }

        for (EvalGroup evalGroup : beEvalGroups) {
            if (!evalGroups.contains(evalGroup) && !EvalConstants.GROUP_TYPE_ADHOC.equals(evalGroup.type)) {
                evalGroups.add(evalGroup);
            }
        }

        if (evalGroups.size() > 0) {
            Map<String, EvalGroup> groupsMap = new HashMap<>();
            for (int i = 0; i < evalGroups.size(); i++) {
                EvalGroup c = (EvalGroup) evalGroups.get(i);
                groupsMap.put(c.evalGroupId, c);
            }

            /*
             * Area 1. Selection GUI for Hierarchy Nodes and Evaluation Groups
             */
            if (showHierarchy) {
                //need to determine who can see what nodes in the hierarchy:
                //null means the user is an admin, empty means the user has no access
                Set<String> parentNodes = null;
                Set<String> accessNodes = null;
                if (!commonLogic.isUserAdmin(currentUserId)) {
                    parentNodes = new HashSet<>();
                    accessNodes = new HashSet<>();
                    Set<EvalHierarchyNode> nodes = hierarchyLogic.getNodesForUserPerm(currentUserId,
                            EvalConstants.HIERARCHY_PERM_ASSIGN_EVALUATION);
                    for (EvalHierarchyNode node : nodes) {
                        accessNodes.add(node.id);
                        accessNodes.addAll(node.childNodeIds);
                        parentNodes.add(node.id);
                        parentNodes.addAll(node.parentNodeIds);
                    }
                }

                UIBranchContainer hierarchyArea = UIBranchContainer.make(form, "hierarchy-node-area:");

                hierUtil.renderSelectHierarchyNodesTree(hierarchyArea, "hierarchy-tree-select:", evalGroupsSelectID,
                        hierNodesSelectID, evalGroupsLabels, evalGroupsValues, hierNodesLabels, hierNodesValues,
                        evalViewParams, accessNodes, parentNodes, evaluation.getSectionAwareness());

                addCollapseControl(tofill, hierarchyArea, "initJSHierarchyToggle", "hierarchy-assignment-area",
                        "hide-button", "show-button", evalViewParams.expanded == null);

                form.parameters.add(new UIELBinding(actionBean + "expanded", evalViewParams.expanded));
            }

            /*
             * Area 2. display checkboxes for selecting the non-hierarchy groups
             */
            UIBranchContainer evalgroupArea = UIBranchContainer.make(form, "evalgroups-area:");

            // If both the hierarchy and adhoc groups are disabled, don't hide the
            // selection area and don't make it collapsable, since it will be the
            // only thing on the screen.
            if (!showHierarchy && !useAdHocGroups) {
                UIOutput.make(evalgroupArea, "evalgroups-assignment-area");
            } else {
                addCollapseControl(tofill, evalgroupArea, "initJSGroupsToggle", "evalgroups-assignment-area",
                        "hide-button", "show-button", true);
            }

            String[] nonAssignedEvalGroupIDs = getEvalGroupIDsNotAssignedInHierarchy(evalGroups)
                    .toArray(new String[] {});

            List<EvalGroup> unassignedEvalGroups = new ArrayList<>();
            for (String nonAssignedEvalGroupID : nonAssignedEvalGroupIDs) {
                unassignedEvalGroups.add(groupsMap.get(nonAssignedEvalGroupID));
            }

            if (!unassignedEvalGroups.isEmpty()) {

                // sort the list by title 
                Collections.sort(unassignedEvalGroups, new ComparatorsUtils.GroupComparatorByTitle());

                //Move current site to top of this list EVALSYS-762.
                int count2 = 0;
                for (EvalGroup group : unassignedEvalGroups) {
                    if (group.evalGroupId.equals(currentEvalGroupId)) {
                        unassignedEvalGroups.remove(count2);
                        unassignedEvalGroups.add(0, group);
                        break;
                    }
                    count2++;
                }

                List<String> assignGroupsIds = new ArrayList<>();
                String groupSelectionOTP = "assignGroupSelectionSettings.";
                if (!newEval) {
                    Map<Long, List<EvalAssignGroup>> selectedGroupsMap = evaluationService
                            .getAssignGroupsForEvals(new Long[] { evalViewParams.evaluationId }, true, null);
                    List<EvalAssignGroup> assignGroups = selectedGroupsMap.get(evalViewParams.evaluationId);
                    for (EvalAssignGroup assGroup : assignGroups) {
                        assignGroupsIds.add(assGroup.getEvalGroupId());

                        //Add group selection settings to form to support EVALSYS-778
                        if (useSelectionOptions) {
                            Map<String, String> selectionOptions = assGroup.getSelectionOptions();
                            form.parameters.add(new UIELBinding(
                                    groupSelectionOTP + assGroup.getEvalGroupId()
                                            .replaceAll(EvalConstants.GROUP_ID_SITE_PREFIX, "") + ".instructor",
                                    selectionOptions.get(EvalAssignGroup.SELECTION_TYPE_INSTRUCTOR)));
                            form.parameters.add(new UIELBinding(
                                    groupSelectionOTP + assGroup.getEvalGroupId()
                                            .replaceAll(EvalConstants.GROUP_ID_SITE_PREFIX, "") + ".assistant",
                                    selectionOptions.get(EvalAssignGroup.SELECTION_TYPE_ASSISTANT)));
                        }
                    }
                }

                int count = 0;
                int countUnpublishedGroups = 0;
                for (EvalGroup evalGroup : unassignedEvalGroups) {
                    if (evalGroup != null) {

                        String evalGroupId = evalGroup.evalGroupId;

                        boolean hasEvaluators = true;

                        if (!EvalConstants.EVALUATION_AUTHCONTROL_NONE.equals(evaluation.getAuthControl())) {
                            int numEvaluatorsInSite = commonLogic.countUserIdsForEvalGroup(evalGroupId,
                                    EvalConstants.PERM_TAKE_EVALUATION, evaluation.getSectionAwareness());
                            hasEvaluators = numEvaluatorsInSite > 0;
                        }

                        boolean isPublished = commonLogic.isEvalGroupPublished(evalGroupId);

                        UIBranchContainer checkboxRow = UIBranchContainer.make(evalgroupArea, "groups:",
                                count + "");
                        if (count % 2 == 0) {
                            checkboxRow.decorate(new UIStyleDecorator("itemsListOddLine")); // must match the existing CSS class
                        }
                        checkboxRow.decorate(new UIFreeAttributeDecorator("rel", count + "")); // table row counter for JS use in EVALSYS-618

                        //keep deselected user info as a result of changes in EVALSYS-660
                        Set<String> deselectedInsructorIds = new HashSet<>();
                        Set<String> deselectedAssistantIds = new HashSet<>();

                        //Assign attribute to row to help JS set checkbox selection to true
                        if (assignGroupsIds.contains(evalGroupId)) {
                            checkboxRow.decorate(new UIStyleDecorator("selectedGroup"));
                        }

                        if (useSelectionOptions) {

                            if (!newEval) {
                                //Get saved selection settings for this eval
                                List<EvalAssignUser> deselectedInsructors = evaluationService
                                        .getParticipantsForEval(evalViewParams.evaluationId, null,
                                                new String[] { evalGroupId }, EvalAssignUser.TYPE_EVALUATEE,
                                                EvalAssignUser.STATUS_REMOVED, null, null);
                                List<EvalAssignUser> deselectedAssistants = evaluationService
                                        .getParticipantsForEval(evalViewParams.evaluationId, null,
                                                new String[] { evalGroupId }, EvalAssignUser.TYPE_ASSISTANT,
                                                EvalAssignUser.STATUS_REMOVED, null, null);

                                //check for already deselected users that match this groupId
                                for (EvalAssignUser deselectedUser : deselectedInsructors) {
                                    deselectedInsructorIds.add(deselectedUser.getUserId());
                                }
                                for (EvalAssignUser deselectedUser : deselectedAssistants) {
                                    deselectedAssistantIds.add(deselectedUser.getUserId());
                                }

                            } else {
                                //add blank selection options for this group for use by evalAssign.js
                                form.parameters
                                        .add(new UIELBinding(
                                                groupSelectionOTP + evalGroupId.replaceAll(
                                                        EvalConstants.GROUP_ID_SITE_PREFIX, "") + ".instructor",
                                                ""));
                                form.parameters
                                        .add(new UIELBinding(
                                                groupSelectionOTP + evalGroupId.replaceAll(
                                                        EvalConstants.GROUP_ID_SITE_PREFIX, "") + ".assistant",
                                                ""));
                            }
                        }

                        evalGroupsLabels.add(evalGroup.title);
                        evalGroupsValues.add(evalGroupId);

                        String evalUsersLocator = "selectedEvaluationUsersLocator.";

                        UISelectChoice choice = UISelectChoice.make(checkboxRow, "evalGroupId", evalGroupsSelectID,
                                evalGroupsLabels.size() - 1);

                        if (!hasEvaluators) {
                            choice.decorate(new UIDisabledDecorator());
                        }

                        if (useSelectionOptions) {
                            form.parameters.add(new UIELBinding(
                                    evalUsersLocator
                                            + evalGroupId.replaceAll(EvalConstants.GROUP_ID_SITE_PREFIX, "")
                                            + ".deselectedInstructors",
                                    deselectedInsructorIds.toArray(new String[deselectedInsructorIds.size()])));
                            form.parameters.add(new UIELBinding(
                                    evalUsersLocator
                                            + evalGroupId.replaceAll(EvalConstants.GROUP_ID_SITE_PREFIX, "")
                                            + ".deselectedAssistants",
                                    deselectedAssistantIds.toArray(new String[deselectedAssistantIds.size()])));

                            //add ordering bindings
                            form.parameters.add(new UIELBinding(evalUsersLocator
                                    + evalGroupId.replaceAll(EvalConstants.GROUP_ID_SITE_PREFIX, "")
                                    + ".orderingInstructors", new String[] {}));
                            form.parameters.add(new UIELBinding(evalUsersLocator
                                    + evalGroupId.replaceAll(EvalConstants.GROUP_ID_SITE_PREFIX, "")
                                    + ".orderingAssistants", new String[] {}));
                        }

                        // get title from the map since it is faster
                        UIOutput title = UIOutput.make(checkboxRow, "groupTitle", evalGroup.title);

                        if (!isPublished) {
                            title.decorate(new UIStyleDecorator("elementAlertBack"));
                            countUnpublishedGroups++;
                        }

                        if (useSelectionOptions) {
                            if (hasEvaluators) {
                                int totalUsers = commonLogic.countUserIdsForEvalGroup(evalGroupId,
                                        EvalConstants.PERM_BE_EVALUATED, evaluation.getSectionAwareness());
                                if (totalUsers > 0 && hasInstructorQuestions) {
                                    int currentUsers = deselectedInsructorIds.size() >= 0
                                            ? (totalUsers - deselectedInsructorIds.size())
                                            : totalUsers;
                                    UIInternalLink link = UIInternalLink.make(checkboxRow, "select-instructors",
                                            UIMessage.make("assignselect.instructors.select",
                                                    new Object[] { currentUsers, totalUsers }),
                                            new EvalViewParameters(EvaluationAssignSelectProducer.VIEW_ID,
                                                    evaluation.getId(), evalGroupId,
                                                    EvalAssignGroup.SELECTION_TYPE_INSTRUCTOR));
                                    link.decorate(new UIStyleDecorator("addItem total:" + totalUsers));
                                    link.decorate(new UITooltipDecorator(
                                            messageLocator.getMessage("assignselect.instructors.page.title")));
                                }
                                totalUsers = commonLogic.countUserIdsForEvalGroup(evalGroup.evalGroupId,
                                        EvalConstants.PERM_ASSISTANT_ROLE, evaluation.getSectionAwareness());
                                if (totalUsers > 0 && hasAssistantQuestions) {
                                    int currentUsers = deselectedAssistantIds.size() >= 0
                                            ? (totalUsers - deselectedAssistantIds.size())
                                            : totalUsers;
                                    UIInternalLink link = UIInternalLink.make(checkboxRow, "select-tas",
                                            UIMessage.make("assignselect.tas.select",
                                                    new Object[] { currentUsers, totalUsers }),
                                            new EvalViewParameters(EvaluationAssignSelectProducer.VIEW_ID,
                                                    evaluation.getId(), evalGroup.evalGroupId,
                                                    EvalAssignGroup.SELECTION_TYPE_ASSISTANT));
                                    link.decorate(new UIStyleDecorator("addItem total:" + totalUsers));
                                    link.decorate(new UITooltipDecorator(
                                            messageLocator.getMessage("assignselect.tas.page.title")));
                                }
                            } else {
                                title.decorate(new UIStyleDecorator("instruction"));
                                UIMessage.make(checkboxRow, "select-no", "assigneval.cannot.assign");
                            }
                        }

                        UILabelTargetDecorator.targetLabel(title, choice); // make title a label for checkbox

                        count++;
                    }
                }
                if (countUnpublishedGroups > 0) {
                    UIMessage.make(tofill, "assign-eval-instructions-group-notpublished",
                            "assigneval.assign.instructions.notpublished");
                }

            }

        } else {
            // TODO tell user there are no groups to assign to
        }

        /*
         * Area 3: Selection GUI for Adhoc Groups
         */
        String[] adhocGroupRowIds = new String[] {};
        if (useAdHocGroups) {
            UIBranchContainer adhocGroupsArea = UIBranchContainer.make(form, "use-adhoc-groups-area:");

            UIMessage.make(adhocGroupsArea, "adhoc-groups-deleted", "modifyadhocgroup.group.deleted");
            addCollapseControl(tofill, adhocGroupsArea, "initJSAdhocToggle", "adhocgroups-assignment-area",
                    "hide-button", "show-button", false);

            // Table of Existing adhoc groups for selection
            List<EvalAdhocGroup> myAdhocGroups = commonLogic.getAdhocGroupsForOwner(currentUserId);
            if (myAdhocGroups.size() > 0) {
                UIOutput.make(adhocGroupsArea, "adhoc-groups-table");

                ArrayList<String> adhocGroupRowIdsArray = new ArrayList<>(myAdhocGroups.size());
                int count = 0;
                for (EvalAdhocGroup adhocGroup : myAdhocGroups) {
                    UIBranchContainer tableRow = UIBranchContainer.make(adhocGroupsArea, "groups:");
                    adhocGroupRowIdsArray.add(tableRow.getFullID());
                    if (count % 2 == 0) {
                        tableRow.decorate(new UIStyleDecorator("itemsListOddLine")); // must match the existing CSS class
                    }

                    evalGroupsLabels.add(adhocGroup.getTitle());
                    evalGroupsValues.add(adhocGroup.getEvalGroupId());

                    UISelectChoice choice = UISelectChoice.make(tableRow, "evalGroupId", evalGroupsSelectID,
                            evalGroupsLabels.size() - 1);

                    // get title from the map since it is faster
                    UIOutput title = UIOutput.make(tableRow, "groupTitle", adhocGroup.getTitle());
                    UILabelTargetDecorator.targetLabel(title, choice); // make title a label for checkbox

                    // Link to allow editing an existing group
                    UIInternalLink.make(tableRow, "editGroupLink",
                            UIMessage.make("assigneval.page.adhocgroups.editgrouplink"),
                            new AdhocGroupParams(ModifyAdhocGroupProducer.VIEW_ID, adhocGroup.getId(),
                                    vsh.getFullURL(evalViewParams)));
                    // add delete option - https://bugs.caret.cam.ac.uk/browse/CTL-1310
                    if (currentUserId.equals(adhocGroup.getOwner()) || commonLogic.isUserAdmin(currentUserId)) {
                        String deleteLink = commonLogic.getEntityURL(AdhocGroupEntityProvider.ENTITY_PREFIX,
                                adhocGroup.getId().toString());
                        UILink.make(tableRow, "deleteGroupLink", UIMessage.make("general.command.delete"),
                                deleteLink);
                    }
                    count++;
                }
                adhocGroupRowIds = adhocGroupRowIdsArray.toArray(new String[adhocGroupRowIdsArray.size()]);
            }
            UIInternalLink.make(adhocGroupsArea, "new-adhocgroup-link",
                    UIMessage.make("assigneval.page.adhocgroups.newgrouplink"),
                    new AdhocGroupParams(ModifyAdhocGroupProducer.VIEW_ID, null, vsh.getFullURL(evalViewParams)));
        }

        // Add all the groups and hierarchy nodes back to the UISelect Many's. see
        // the large comment further up.
        evalGroupsSelect.optionlist = UIOutputMany.make(evalGroupsValues.toArray(new String[] {}));
        evalGroupsSelect.optionnames = UIOutputMany.make(evalGroupsLabels.toArray(new String[] {}));

        hierarchyNodesSelect.optionlist = UIOutputMany.make(hierNodesValues.toArray(new String[] {}));
        hierarchyNodesSelect.optionnames = UIOutputMany.make(hierNodesLabels.toArray(new String[] {}));

        form.parameters.add(new UIELBinding(actionBean + "evaluationId", evalViewParams.evaluationId));

        UIMessage.make(form, "back-button", "general.back.button");

        if (useSelectionOptions) {
            UIOutput.make(tofill, "JS-facebox");
            UIOutput.make(tofill, "JS-facebox-assign");
            UIOutput.make(tofill, "JS-assign");
            UIMessage.make(form, "select-column-title", "assignselect.page.column.title");
            form.type = EarlyRequestParser.ACTION_REQUEST;
            UICommand.make(form, "confirmAssignCourses", UIMessage.make("evaluationassignconfirm.done.button"),
                    actionBean + "completeConfirmAction");
        } else {
            // this is a get form which does not submit to a backing bean
            EvalViewParameters formViewParams = (EvalViewParameters) evalViewParams.copyBase();
            formViewParams.viewID = EvaluationAssignConfirmProducer.VIEW_ID;
            form.viewparams = formViewParams;
            form.type = EarlyRequestParser.RENDER_REQUEST;
            // all command buttons are just HTML now so no more bindings
            UIMessage assignButton = UIMessage.make(form, "confirmAssignCourses",
                    "assigneval.save.assigned.button");

            // activate the adhoc groups deletion javascript
            if (useAdHocGroups) {
                UIInitBlock.make(tofill, "initJavaScriptAdhoc", "EvalSystem.initEvalAssignAdhocDelete",
                        new Object[] { adhocGroupRowIds });
            }

            // Error message to be triggered by javascript if users doesn't select anything
            // There is a 'evalgroupselect' class on each input checkbox that the JS
            // can check for now.
            UIMessage assignErrorDiv = UIMessage.make(tofill, "nogroups-error", "assigneval.invalid.selection");

            boolean anonymousAllowed = false;
            if (EvalConstants.EVALUATION_AUTHCONTROL_NONE.equals(evaluation.getAuthControl())) {
                anonymousAllowed = true;
            }
            UIInitBlock.make(tofill, "initJavaScript", "EvalSystem.initEvalAssignValidation", new Object[] {
                    form.getFullID(), assignErrorDiv.getFullID(), assignButton.getFullID(), anonymousAllowed });
        }

    }

    /**
     * I think this is getting all the groupIds that are not currently assigned to nodes in the hierarchy
     * 
     * @param evalGroups the list of eval groups to check in
     * @return the set of evalGroupsIds from the input list of evalGroups which are not assigned to hierarchy nodes
     */
    protected Set<String> getEvalGroupIDsNotAssignedInHierarchy(List<EvalGroup> evalGroups) {
        // TODO - we probably need a method to simply get all assigned groupIds in the hierarchy to make this a bit faster

        // 1. All the Evaluation Group IDs in a set
        Set<String> evalGroupIDs = new HashSet<>();
        for (EvalGroup evalGroup : evalGroups) {
            evalGroupIDs.add(evalGroup.evalGroupId);
        }
        /*
                // 2. All the Evaluation Group IDs that are assigned to Hierarchy Nodes
                EvalHierarchyNode rootNode = hierarchyLogic.getRootLevelNode();
                String[] rootNodeChildren = rootNode.childNodeIds.toArray(new String[] {});
                if (rootNodeChildren.length > 0) {
        Map<String,Set<String>> assignedGroups = hierarchyLogic.getEvalGroupsForNodes(rootNodeChildren);
            
        Set<String> hierAssignedGroupIDs = new HashSet<String>();
        for (String key: assignedGroups.keySet()) {
            hierAssignedGroupIDs.addAll(assignedGroups.get(key));
        }
        // 3. Remove all EvalGroup IDs that have been assigned to 
        evalGroupIDs.removeAll(hierAssignedGroupIDs);
                }
        */

        return evalGroupIDs;
    }

    /**
     * Taking the parent container and rsf:id's for the collapsed area and tags
     * to show and hide, creates the necessary javascript.  The Javascript is
     * appended to the instance variable holding the javascript that will be
     * rendered at the bottom of the page for javascript initialization.
     */
    private void addCollapseControl(UIContainer tofill, UIContainer parent, String rsfId, String areaId,
            String hideId, String showId, boolean initialHide) {
        UIOutput hideControl = UIOutput.make(parent, hideId);
        UIOutput showControl = UIOutput.make(parent, showId);
        UIOutput areaDiv = UIOutput.make(parent, areaId);

        //makeToggle: function (showId, hideId, areaId, toggleClass, initialHide) {
        if (initialHide) {
            UIInitBlock.make(tofill, rsfId, "EvalSystem.makeToggle", new Object[] { showControl.getFullID(),
                    hideControl.getFullID(), areaDiv.getFullID(), null, initialHide });
        } else {
            UIInitBlock.make(tofill, rsfId, "EvalSystem.makeToggle",
                    new Object[] { showControl.getFullID(), hideControl.getFullID(), areaDiv.getFullID() });
        }
    }

    /* (non-Javadoc)
     * @see uk.org.ponder.rsf.flow.ActionResultInterceptor#interceptActionResult(uk.org.ponder.rsf.flow.ARIResult, uk.org.ponder.rsf.viewstate.ViewParameters, java.lang.Object)
     */
    public void interceptActionResult(ARIResult result, ViewParameters incoming, Object actionReturn) {
        // handles the navigation cases and passing along data from view to view
        EvalViewParameters evp = (EvalViewParameters) incoming;
        Long evalId = evp.evaluationId;
        if ("evalSettings".equals(actionReturn)) {
            result.resultingView = new EvalViewParameters(EvaluationSettingsProducer.VIEW_ID, evalId);
        } else if ("evalAssign".equals(actionReturn)) {
            result.resultingView = new EvalViewParameters(EvaluationAssignProducer.VIEW_ID, evalId);
        } else if ("evalConfirm".equals(actionReturn)) {
            result.resultingView = new EvalViewParameters(EvaluationAssignConfirmProducer.VIEW_ID, evalId);
        } else if ("controlEvals".equals(actionReturn)) {
            result.resultingView = new SimpleViewParameters(ControlEvaluationsProducer.VIEW_ID);
        }
    }

    /* (non-Javadoc)
     * @see uk.org.ponder.rsf.viewstate.ViewParamsReporter#getViewParameters()
     */
    public ViewParameters getViewParameters() {
        return new EvalViewParameters();
    }

}