/* * Copyright (C) NetStruxr, Inc. All rights reserved. * * This software is published under the terms of the NetStruxr * Public Software License version 0.5, a copy of which has been * included with this distribution in the LICENSE.NPL file. */ package er.extensions.components; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.appserver.WOContext; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSKeyValueCoding; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; /** * Groups items into sections.For example: Employees belong to a department, you want to * group on department. So the parent will need to consist of something like: * <pre> * * [erxgroupingrepetition] * [wostring value=currentDepartment.name] * [worepetition list=currentEmployees item=currentEmployee] * [wostring value=currentEmployees.firstName] * [/worepetition] * [/erxgroupingrepetition] * * </pre> * and then you'd set up the bindings of the grouping repetition like: * <pre> * * list=allEmployees : list of employees to group * item=currentEmployee : will be set so the next key can get evaluated * sectionForItem=departmentForCurrentEmployee : a method in the parent that returns sth like currentEmployee.department() * sectionKey="name" : assuming department has a name, but can be unbound; note that you can group on "city", too! * subListSection=currentDepartment : instance variable in the parent that will get set to the current department * subList=currentEmployees : instance variable in the parent that will get set to the employees of the current department * sortKey="@sortAsc.name" : sorts the department list by name * * </pre> * If a user could belong to many departments, you could either set the <code>splitArrays</code> binding to true, * in which case the sections would be all the departments and the user would be added in each section he belongs * or you could leave it out. Then the sections will be each combination of departments a user belongs to. * Please see the page BugsPerUser.wo from the BugTracker application to find another example on how to use it. * @binding list list of objects to group * @binding item current item, will get pushed to the parent, so that it can evaluate sectionForItem * @binding sectionForItem value pulled from the parent, after "item" gets pushed * @binding sectionKey key to group departments on (usually primaryKey or hashCode) * @binding subListSection will get set to the current section * @binding subList will get set to the grouped items for the section * @binding sortKey optional key for sorting the group list (sth like '@sortAsc.name') * @binding splitArrays optional boolean specifying if array keys are regarded as distinct keys * @binding ignoreNulls optional boolean specifying if nulls are ignored */ public class ERXGroupingRepetition extends ERXStatelessComponent { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; public ERXGroupingRepetition(WOContext aContext) { super(aContext); } private static final Logger log = LoggerFactory.getLogger(ERXGroupingRepetition.class); private NSMutableArray _sections; private Object _sectionItem; private NSMutableDictionary _itemsPerSection=new NSMutableDictionary(); private final static Object NULL="N/A"; private String _sectionKey; public String sectionKey() { if (_sectionKey==null) { _sectionKey=stringValueForBinding("sectionKey", "hashCode"); } return _sectionKey; } public NSArray sections() { if (_sections==null) { _sections= new NSMutableArray(); NSArray list=(NSArray)valueForBinding("list"); _itemsPerSection=new NSMutableDictionary(); if (list!=null) { boolean ignoreNulls = booleanValueForBinding("ignoreNulls", false); for (Enumeration e=list.objectEnumerator(); e.hasMoreElements();) { Object item=e.nextElement(); log.debug("item = {}", item); // push value up, so parent can tell us the group setValueForBinding(item,"item"); // Sections have to be copiable objects -- no EOs!! Object section=valueForBinding("sectionForItem"); if (section==NSKeyValueCoding.NullValue) section=null; Object sectionKey; if(section == null) { if(ignoreNulls) { continue; } section=NULL; } sectionKey = keyForSection(section); if(sectionKey instanceof NSArray) { NSArray array = (NSArray)sectionKey; int index = 0; for (Enumeration keys = ((NSArray)sectionKey).objectEnumerator(); keys.hasMoreElements(); ) { Object currentKey = keys.nextElement(); Object currentSection = ((NSArray)section).objectAtIndex(index++); NSMutableArray currentItemsForSection=(NSMutableArray)_itemsPerSection.objectForKey(currentKey); if (currentItemsForSection==null) { _sections.addObject(currentSection); currentItemsForSection=new NSMutableArray(); _itemsPerSection.setObjectForKey(currentItemsForSection,currentKey); } currentItemsForSection.addObject(item); } } else { NSMutableArray currentItemsForSection=(NSMutableArray)_itemsPerSection.objectForKey(sectionKey); if (currentItemsForSection==null) { _sections.addObject(section); currentItemsForSection=new NSMutableArray(); _itemsPerSection.setObjectForKey(currentItemsForSection,sectionKey); } currentItemsForSection.addObject(item); } } } String sortKey = (String)valueForBinding("sortKey"); //the key act on the array, so it must be in the form "@sortAsc.someKey" if(sortKey != null) { _sections = (NSMutableArray)_sections.valueForKeyPath(sortKey); } } return _sections; } /** * @param splitArrays * @param section */ private Object keyForSection(Object section) { Object sectionKey = NULL; if(section != null && section != NULL) { sectionKey = NSKeyValueCodingAdditions.Utility.valueForKeyPath(section,sectionKey()); if(!splitArrays() && (sectionKey instanceof NSArray)) { sectionKey = ((NSArray)((NSArray)section).valueForKey(_sectionKey)).componentsJoinedByString(","); } } return sectionKey; } private Boolean _splitArrays; private boolean splitArrays() { if(_splitArrays == null) { _splitArrays = booleanValueForBinding("splitArrays", false) ? Boolean.TRUE : Boolean.FALSE; } return _splitArrays.booleanValue(); } public Object sectionItem() { return _sectionItem; } public void setSectionItem(Object section) { _sectionItem=section; setValueForBinding(_sectionItem!=NULL ? _sectionItem : null, "subListSection"); setValueForBinding(_itemsPerSection.objectForKey(keyForSection(_sectionItem)), "subList"); } @Override public void reset() { _sections=null; _splitArrays=null; _sectionItem=null; _sectionKey=null; _itemsPerSection=null; } }