/* * 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.directtoweb.pages; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Objects; import org.apache.log4j.Logger; import com.webobjects.appserver.WOActionResults; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WODisplayGroup; import com.webobjects.appserver.WORequest; import com.webobjects.appserver.WOResponse; import com.webobjects.appserver.WOSession; import com.webobjects.directtoweb.ConfirmPageInterface; import com.webobjects.directtoweb.D2W; import com.webobjects.directtoweb.D2WContext; import com.webobjects.directtoweb.D2WListPage; import com.webobjects.directtoweb.EditPageInterface; import com.webobjects.directtoweb.InspectPageInterface; import com.webobjects.directtoweb.ListPageInterface; import com.webobjects.directtoweb.SelectPageInterface; import com.webobjects.eoaccess.EODatabaseDataSource; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eoaccess.EOUtilities; import com.webobjects.eocontrol.EODataSource; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.eocontrol.EOFetchSpecification; import com.webobjects.eocontrol.EOSharedEditingContext; import com.webobjects.eocontrol.EOSortOrdering; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSKeyValueCoding; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSNotification; import com.webobjects.foundation.NSNotificationCenter; import com.webobjects.foundation.NSSelector; import er.directtoweb.ERD2WFactory; import er.directtoweb.delegates.ERDDeletionDelegate; import er.directtoweb.interfaces.ERDEditObjectDelegate; import er.directtoweb.interfaces.ERDListPageInterface; import er.extensions.appserver.ERXComponentActionRedirector; import er.extensions.appserver.ERXDisplayGroup; import er.extensions.appserver.ERXSession; import er.extensions.batching.ERXBatchingDisplayGroup; import er.extensions.eof.ERXConstant; import er.extensions.eof.ERXEOAccessUtilities; import er.extensions.eof.ERXEOControlUtilities; import er.extensions.foundation.ERXArrayUtilities; import er.extensions.foundation.ERXValueUtilities; import er.extensions.localization.ERXLocalizer; import er.extensions.statistics.ERXStats; /** * Reimplementation of the D2WListPage. Descends from ERD2WPage instead of * D2WList. * * @author ak * @d2wKey useBatchingDisplayGroup * @d2wKey isEntityEditable * @d2wKey readOnly * @d2wKey alwaysRefetchList * @d2wKey pageConfiguration * @d2wKey defaultBatchSize * @d2wKey subTask * @d2wKey checkSortOrderingKeys * @d2wKey defaultSortOrdering * @d2wKey displayPropertyKeys * @d2wKey restrictingFetchSpecification * @d2wKey isEntityInspectable * @d2wKey isEntityPrintable * @d2wKey confirmDeleteConfigurationName * @d2wKey editConfigurationName * @d2wKey inspectConfigurationName * @d2wKey useNestedEditingContext * @d2wKey targetDictionary * @d2wKey shouldShowSelectAll * @d2wKey referenceRelationshipForBackgroupColor * @d2wKey showBatchNavigation */ public class ERD2WListPage extends ERD2WPage implements ERDListPageInterface, SelectPageInterface, ERXComponentActionRedirector.Restorable { /** * 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; /** logging support */ public final static Logger log = Logger.getLogger(ERD2WListPage.class); protected boolean _shouldRefetch; protected String _sessionID; /** * Public constructor. Registers for * {@link EOEditingContext#EditingContextDidSaveChangesNotification} so that * component stays informed when objects are deleted and added. * * @param c * current context */ public ERD2WListPage(WOContext c) { super(c); _sessionID = c.session().sessionID(); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector<Void>("editingContextDidSaveChanges", ERXConstant.NotificationClassArray), EOEditingContext.EditingContextDidSaveChangesNotification, null); } /* Not necessary -- NSNotificationCenter uses weak references public void finalize() throws Throwable { NSNotificationCenter.defaultCenter().removeObserver(this); super.finalize(); } */ // reimplementation of D2WList stuff /** Holds the display group. */ protected WODisplayGroup _displayGroup; public boolean _hasToUpdate = false; protected boolean _rowFlip = false; /** Returns the display group, creating one if there is none present. */ public WODisplayGroup displayGroup() { if (_displayGroup == null) { createDisplayGroup(); _displayGroup.setSelectsFirstObjectAfterFetch(false); if (ERD2WFactory.erFactory().defaultListPageDisplayGroupDelegate() != null) { _displayGroup.setDelegate(ERD2WFactory.erFactory().defaultListPageDisplayGroupDelegate()); } } return _displayGroup; } /** * Creates the display group and sets the _displayGroup instance variable */ protected void createDisplayGroup() { boolean useBatchingDisplayGroup = useBatchingDisplayGroup(); if (useBatchingDisplayGroup) { _displayGroup = new ERXBatchingDisplayGroup(); ((ERXBatchingDisplayGroup) _displayGroup).setShouldRememberRowCount(false); } else { _displayGroup = new ERXDisplayGroup(); } } /** * Checks the d2wContext for useBatchingDisplayGroup and returns it. * */ public boolean useBatchingDisplayGroup() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("useBatchingDisplayGroup")); } /** * Cached session ID, so we don't need to awake. */ @Override public String sessionID() { return _sessionID; } /** * Called when an {@link EOEditingContext} has changed. Sets * {@link #_hasToUpdate} which in turn lets the group refetch on the next * display. */ public void editingContextDidSaveChanges(NSNotification notif) { if (Objects.equals(sessionID(), ERXSession.currentSessionID())) { _hasToUpdate = true; } } /** * Checks if the entity is read only, meaning that you can't edit it's * objects. */ @Override public boolean isEntityReadOnly() { boolean flag = super.isEntityReadOnly(); flag = !ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("isEntityEditable"), !flag); flag = ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("readOnly"), flag); return flag; } @Override public boolean isEntityEditable() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("isEntityEditable"), false); } public boolean alwaysRefetchList() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("alwaysRefetchList"), true); } /** * Checks if the current task is select. We need this because this page * implements the {@link SelectPageInterface} so we can't do an instanceof * test. */ public boolean isSelecting() { return task().equals("select"); } /** Checks if the current list is empty. */ public boolean isListEmpty() { return listSize() == 0; } /** The number of objects in the list. */ public int listSize() { return displayGroup().displayedObjects().count(); } /** * Utility to have alternating row colors. Override this to have more than * one color. */ public String alternatingColorForRow() { _rowFlip = !_rowFlip; if (_rowFlip || !alternateRowColor()) return backgroundColorForTable(); else return backgroundColorForTableDark(); } /** * The background color for the current row. Override this to have more than * one color. */ public String backgroundColorForRow() { return !isSelecting() || selectedObjects().containsObject(object()) ? alternatingColorForRow() : "#FFFF00"; } /** Does nothing and exists only for KeyValueCoding. */ public void setBackgroundColorForRow(String value) { } /** The currently selected object. */ public EOEnterpriseObject selectedObject() { return (EOEnterpriseObject) displayGroup().selectedObject(); } /** * Sets currently selected object. Pushes the value to the display group, * clearing the selection if needed. */ public void setSelectedObject(EOEnterpriseObject eo) { if (eo != null) displayGroup().selectObject(eo); else displayGroup().clearSelection(); } /** The currently selected objects. */ public NSArray selectedObjects() { return displayGroup().selectedObjects(); } /** * Sets currently selected objects. Pushes the values to the display group, * clearing the selection if needed. */ public void setSelectedObjects(NSArray eos) { if (eos != null) displayGroup().setSelectedObjects(eos); else displayGroup().clearSelection(); } /** Action method to select an object. */ public WOComponent selectObjectAction() { setSelectedObject(object()); WOComponent result = nextPageFromDelegate(); return result; } public WOComponent backAction() { WOComponent result = nextPageFromDelegate(); if (result == null) { result = nextPage(); if (result == null) { result = (WOComponent) D2W.factory().queryPageForEntityNamed(entity().name(), session()); } } return result; } /** * end of reimplementation */ @Override public String urlForCurrentState() { return context().directActionURLForActionNamed(d2wContext().dynamicPage(), null).replaceAll("&", "&"); } protected void setSortOrderingsOnDisplayGroup(NSArray sortOrderings, WODisplayGroup dg) { sortOrderings = sortOrderings != null ? sortOrderings : NSArray.EmptyArray; dg.setSortOrderings(sortOrderings); } public static WOComponent printerFriendlyVersion(D2WContext d2wContext, WOSession session, EODataSource dataSource, WODisplayGroup displayGroup) { ListPageInterface result = (ListPageInterface) ERD2WFactory.erFactory().printerFriendlyPageForD2WContext(d2wContext, session); result.setDataSource(dataSource); WODisplayGroup dg = null; if (result instanceof D2WListPage) { dg = ((D2WListPage) result).displayGroup(); } else if (result instanceof ERDListPageInterface) { dg = ((ERDListPageInterface) result).displayGroup(); } else { try { dg = (WODisplayGroup) ((WOComponent) result).valueForKey("displayGroup"); } catch (Exception ex) { log.warn("Can't get displayGroup from page of class: " + result.getClass().getName()); } } if (dg != null) { dg.setSortOrderings(displayGroup.sortOrderings()); dg.setNumberOfObjectsPerBatch(displayGroup.allObjects().count()); dg.updateDisplayedObjects(); } return (WOComponent) result; } public WOComponent printerFriendlyVersion() { return ERD2WListPage.printerFriendlyVersion(d2wContext(), session(), dataSource(), displayGroup()); } // This will allow d2w pages to be listed on a per configuration basis in // stats collecting. @Override public String descriptionForResponse(WOResponse aResponse, WOContext aContext) { String descriptionForResponse = (String) d2wContext().valueForKey("pageConfiguration"); /* * if (descriptionForResponse == null) log.info("Unable to find * pageConfiguration in d2wContext: " + d2wContext()); */ return descriptionForResponse != null ? descriptionForResponse : super.descriptionForResponse(aResponse, aContext); } private boolean _hasBeenInitialized = false; private Number _batchSize = null; public int numberOfObjectsPerBatch() { if (_batchSize == null) { if (shouldShowBatchNavigation()) { int batchSize = ERXValueUtilities.intValueWithDefault(d2wContext().valueForKey("defaultBatchSize"), 0); Object batchSizePref = userPreferencesValueForPageConfigurationKey("batchSize"); if (batchSizePref != null) { if (log.isDebugEnabled()) { log.debug("batchSize User Preference: " + batchSizePref); } batchSize = ERXValueUtilities.intValueWithDefault(batchSizePref, batchSize); } _batchSize = ERXConstant.integerForInt(batchSize); } else { // We are not showing the batch nav, so we need to display all results. _batchSize = ERXConstant.ZeroInteger; } } return _batchSize.intValue(); } // this can be overridden by subclasses for which sorting has to be fixed // (i.e. Grouping Lists) public boolean userPreferencesCanSpecifySorting() { return !"printerFriendly".equals(d2wContext().valueForKey("subTask")); } /** * Returns whether or not sort orderings should be validated (based on the checkSortOrderingKeys rule). * @return whether or not sort orderings should be validated */ public boolean checkSortOrderingKeys() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("checkSortOrderingKeys"), false); } /** * Validates the given sort key (is it a display key, an attribute, or a valid attribute path). * * @param displayPropertyKeys the current display properties * @param sortKey the sort key to validate * @return true if the sort key is valid, false if not */ protected boolean isValidSortKey(NSArray<String> displayPropertyKeys, String sortKey) { boolean validSortOrdering = false; try { if (displayPropertyKeys.containsObject(sortKey) || entity().anyAttributeNamed(sortKey) != null || ERXEOAccessUtilities.attributePathForKeyPath(entity(), sortKey).count() > 0) { validSortOrdering = true; } } catch (IllegalArgumentException e) { // MS: ERXEOAccessUtilities.attributePathForKeyPath throws IllegalArgumentException for a bogus key path validSortOrdering = false; } if (!validSortOrdering) { log.warn("Sort key '" + sortKey + "' is not in display keys, attributes or non-flattened key paths for the entity '" + entity().name() + "'."); validSortOrdering = false; } return validSortOrdering; } @SuppressWarnings("unchecked") public NSArray<EOSortOrdering> sortOrderings() { NSArray<EOSortOrdering> sortOrderings = null; if (userPreferencesCanSpecifySorting()) { sortOrderings = (NSArray<EOSortOrdering>) userPreferencesValueForPageConfigurationKey("sortOrdering"); if (log.isDebugEnabled()) { log.debug("Found sort Orderings in user prefs " + sortOrderings); } } if (sortOrderings == null) { NSArray<String> sortOrderingDefinition = (NSArray<String>) d2wContext().valueForKey("defaultSortOrdering"); if (sortOrderingDefinition != null) { NSMutableArray<EOSortOrdering> validatedSortOrderings = new NSMutableArray<>(); NSArray<String> displayPropertyKeys = (NSArray<String>) d2wContext().valueForKey("displayPropertyKeys"); for (int i = 0; i < sortOrderingDefinition.count();) { String sortKey = sortOrderingDefinition.objectAtIndex(i++); String sortSelectorKey = sortOrderingDefinition.objectAtIndex(i++); if (!checkSortOrderingKeys() || isValidSortKey(displayPropertyKeys, sortKey)) { EOSortOrdering sortOrdering = new EOSortOrdering(sortKey, ERXArrayUtilities.sortSelectorWithKey(sortSelectorKey)); validatedSortOrderings.addObject(sortOrdering); } } sortOrderings = validatedSortOrderings; if (log.isDebugEnabled()) { log.debug("Found sort Orderings in rules " + sortOrderings); } } } return sortOrderings; } public String defaultSortKey() { // the default D2W mechanism is completely disabled return null; } @Override public void takeValuesFromRequest(WORequest r, WOContext c) { setupPhase(); super.takeValuesFromRequest(r, c); } protected void _fetchDisplayGroup(WODisplayGroup dg) { String statsKey = super.makeStatsKey("DisplayGroup Fetch"); ERXStats.markStart(ERXStats.Group.SQL, statsKey); try { dg.fetch(); } catch (NSKeyValueCoding.UnknownKeyException e) { if (dg.sortOrderings() != null && dg.sortOrderings().count() > 0) { log.error("Fetching display group failed. Resetting potentially bogus sort orderings and trying again.", e); dg.setSortOrderings(null); dg.fetch(); } else { throw e; } } ERXStats.markEnd(ERXStats.Group.SQL, statsKey); } protected void fetchIfNecessary() { if (_hasToUpdate) { willUpdate(); _fetchDisplayGroup(displayGroup()); _hasToUpdate = false; didUpdate(); } } @Override public WOActionResults invokeAction(WORequest r, WOContext c) { setupPhase(); fetchIfNecessary(); return super.invokeAction(r, c); } @Override public void appendToResponse(WOResponse r, WOContext c) { setupPhase(); _rowFlip = true; fetchIfNecessary(); // GN: reset the displayed batch if it is out of range if (displayGroup() != null && displayGroup().currentBatchIndex() > displayGroup().batchCount()) { displayGroup().setCurrentBatchIndex(1); } super.appendToResponse(r, c); } protected Object dataSourceState; @Override public void setDataSource(EODataSource eodatasource) { EODatabaseDataSource ds = (eodatasource instanceof EODatabaseDataSource) ? (EODatabaseDataSource) eodatasource : null; Object newDataSourceState = null; if (ds != null) { newDataSourceState = ds.fetchSpecification().toString().replaceAll("\\n", "") + ":" + ds.fetchSpecificationForFetch().toString().replaceAll("\\n", "") + " fetchLimit: " + ds.fetchSpecification().fetchLimit() + ", " + ds.fetchSpecificationForFetch().fetchLimit(); } EODataSource old = displayGroup().dataSource(); super.setDataSource(eodatasource); displayGroup().setDataSource(eodatasource); if (ds == null || (dataSourceState == null) || (dataSourceState != null && !dataSourceState.equals(newDataSourceState)) || alwaysRefetchList()) { log.debug("updating:\n" + dataSourceState + " vs\n" + newDataSourceState); dataSourceState = newDataSourceState; _hasToUpdate = true; // AK: when you use the page in a embedded component and have a few // of them in a tab // page, WO reuses the component for a new dataSource. If this DS // doesn't have the // sort order keys required it leads to a KVC error later on. We fix // this here to re-init // the sort ordering from the rules. if (old != null && eodatasource != null && !Objects.equals(eodatasource.classDescriptionForObjects(), old.classDescriptionForObjects())) { setSortOrderingsOnDisplayGroup(sortOrderings(), displayGroup()); } } } protected void willUpdate() { } protected void didUpdate() { } protected void setupPhase() { WODisplayGroup dg = displayGroup(); if (dg != null) { NSArray sortOrderings = dg.sortOrderings(); EODataSource ds = dataSource(); if (!_hasBeenInitialized) { log.debug("Initializing display group"); String fetchspecName = (String) d2wContext().valueForKey("restrictingFetchSpecification"); if (fetchspecName != null) { if (ds instanceof EODatabaseDataSource) { EOFetchSpecification fs = ((EODatabaseDataSource) ds).entity().fetchSpecificationNamed(fetchspecName); if (fs != null) { fs = (EOFetchSpecification) fs.clone(); } ((EODatabaseDataSource) ds).setFetchSpecification(fs); } } if (sortOrderings == null) { sortOrderings = sortOrderings(); setSortOrderingsOnDisplayGroup(sortOrderings, dg); } dg.setNumberOfObjectsPerBatch(numberOfObjectsPerBatch()); _fetchDisplayGroup(dg); dg.updateDisplayedObjects(); _hasBeenInitialized = true; _hasToUpdate = false; } // AK: if we have a DB datasource, then we might want to refetch if // the sort ordering changed // because if we have a fetch limit then the displayed matches on // the first page come from the // results, not from the real order in the DB. Set // "alwaysRefetchList" to false in your // rules to prevent that. // In addition, we need to refetch if we use a batching display // group, as the sort ordering is // always applied from the DB. if ((sortOrderings != null) && (ds instanceof EODatabaseDataSource)) { EOFetchSpecification fs = ((EODatabaseDataSource) ds).fetchSpecification(); if (!fs.sortOrderings().equals(sortOrderings) && (fs.fetchLimit() != 0 || useBatchingDisplayGroup())) { fs.setSortOrderings(sortOrderings); _hasToUpdate = _hasToUpdate ? true : alwaysRefetchList(); } } // this will have the side effect of resetting the batch # to sth // correct, in case // the current index if out of range log.debug("dg.currentBatchIndex() " + dg.currentBatchIndex()); dg.setCurrentBatchIndex(dg.currentBatchIndex()); if (listSize() > 0 && displayGroup().selectsFirstObjectAfterFetch()) { d2wContext().takeValueForKey(dg.allObjects().objectAtIndex(0), "object"); } } } public boolean isEntityInspectable() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("isEntityInspectable"), isEntityReadOnly()); // return isEntityReadOnly() && (isEntityInspectable!=null && // isEntityInspectable.intValue()!=0); } public boolean isEntityPrintable() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("isEntityPrintable"), false); } public WOComponent deleteObjectAction() { String confirmDeleteConfigurationName = (String) d2wContext().valueForKey("confirmDeleteConfigurationName"); ConfirmPageInterface nextPage; if (confirmDeleteConfigurationName == null) { log.warn("Using default delete template: ERD2WConfirmPageTemplate, set the 'confirmDeleteConfigurationName' key to something more sensible"); nextPage = (ConfirmPageInterface) pageWithName("ERD2WConfirmPageTemplate"); } else { nextPage = (ConfirmPageInterface) D2W.factory().pageForConfigurationNamed(confirmDeleteConfigurationName, session()); } nextPage.setConfirmDelegate(new ERDDeletionDelegate(object(), dataSource(), context().page())); nextPage.setCancelDelegate(new ERDDeletionDelegate(null, null, context().page())); if (nextPage instanceof InspectPageInterface) { ((InspectPageInterface) nextPage).setObject(object()); } else { String message = ERXLocalizer.currentLocalizer().localizedTemplateStringForKeyWithObject("ERD2WList.confirmDeletionMessage", d2wContext()); nextPage.setMessage(message); } return (WOComponent) nextPage; } public WOComponent editObjectAction() { WOComponent result = null; EditPageInterface epi; ERDEditObjectDelegate editObjectDelegate = null; String editConfigurationName = (String) d2wContext().valueForKey("editConfigurationName"); EOEnterpriseObject leo = localInstanceOfObject(); log.debug("editConfigurationName: " + editConfigurationName); if ((editObjectDelegate = editObjectDelegateInstance()) != null) { result = editObjectDelegate.editObject(leo, context().page()); } else { if (editConfigurationName != null) { epi = (EditPageInterface) D2W.factory().pageForConfigurationNamed(editConfigurationName, session()); } else { epi = D2W.factory().editPageForEntityNamed(object().entityName(), session()); } epi.setObject(leo); epi.setNextPage(context().page()); result = (WOComponent) epi; } return result; } public WOComponent inspectObjectAction() { InspectPageInterface ipi; String inspectConfigurationName = (String) d2wContext().valueForKey("inspectConfigurationName"); log.debug("inspectConfigurationName: " + inspectConfigurationName); if (inspectConfigurationName != null) { ipi = (InspectPageInterface) D2W.factory().pageForConfigurationNamed(inspectConfigurationName, session()); } else { ipi = D2W.factory().inspectPageForEntityNamed(object().entityName(), session()); } ipi.setObject(object()); ipi.setNextPage(context().page()); return (WOComponent) ipi; } protected EOEnterpriseObject localInstanceOfObject() { Object value = d2wContext().valueForKey("useNestedEditingContext"); boolean createNestedContext = ERXValueUtilities.booleanValue(value); return ERXEOControlUtilities.editableInstanceOfObject(object(), createNestedContext); } /** * Should we show the cancel button? It's only visible when we have a * nextPage set up. */ @Override public boolean showCancel() { return nextPage() != null; } /** * Returns true of we are selecting, but not the top-level page. * */ public boolean isSelectingNotTopLevel() { boolean result = false; if (isSelecting() && (context().page() != this)) { result = true; } return result; } private String _formTargetJavaScriptUrl; public String formTargetJavaScriptUrl() { if (_formTargetJavaScriptUrl == null) { _formTargetJavaScriptUrl = application().resourceManager().urlForResourceNamed("formTarget.js", "ERDirectToWeb", null, context().request()); } return _formTargetJavaScriptUrl; } public String targetString() { String result = ""; NSDictionary targetDictionary = (NSDictionary) d2wContext().valueForKey("targetDictionary"); if (targetDictionary != null) { StringBuilder buffer = new StringBuilder(); buffer.append(targetDictionary.valueForKey("targetName") != null ? targetDictionary.valueForKey("targetName") : "foobar"); buffer.append(":width="); buffer.append(targetDictionary.valueForKey("width") != null ? targetDictionary.valueForKey("width") : "{window.screen.width/2}"); buffer.append(", height="); buffer.append(targetDictionary.valueForKey("height") != null ? targetDictionary.valueForKey("height") : "{myHeight}"); buffer.append(','); buffer.append((targetDictionary.valueForKey("scrollbars") != null && targetDictionary.valueForKey("scrollbars") == "NO") ? " " : "scrollbars"); buffer.append(", {(isResizable)?'resizable':''}, status"); result = buffer.toString(); } else { result = "foobar:width={window.screen.width/2}, height={myHeight}, scrollbars, {(isResizable)?'resizable':''}, status"; } return result; } public boolean shouldShowSelectAll() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("shouldShowSelectAll"), listSize() > 10); } public void warmUpForDisplay() { // default implementation does nothing } public String colorForRow() { String result = null; if (d2wContext().valueForKey("referenceRelationshipForBackgroupColor") != null) { String path = (String) d2wContext().valueForKey("referenceRelationshipForBackgroupColor") + ".backgroundColor"; result = (String) object().valueForKeyPath(path); } return result; } public EOEnterpriseObject referenceEO; private NSArray _referenceEOs; public NSArray referenceEOs() { if (_referenceEOs == null) { String relationshipName = (String) d2wContext().valueForKey("referenceRelationshipForBackgroupColor"); if (relationshipName != null) { EOEntity entity = EOModelGroup.defaultGroup().entityNamed(entityName()); EORelationship relationship = entity.relationshipNamed(relationshipName); _referenceEOs = EOUtilities.objectsForEntityNamed(EOSharedEditingContext.defaultSharedEditingContext(), relationship.destinationEntity().name()); _referenceEOs = ERXArrayUtilities.sortedArraySortedWithKey(_referenceEOs, "ordering", EOSortOrdering.CompareAscending); } } return _referenceEOs; } /** * Determines if the batch navigation should be shown. It can be explicitly disabled by setting the D2W key * <code>showBatchNavigation</code> to false. * @return true if the batch navigation should be shown */ public boolean shouldShowBatchNavigation() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("showBatchNavigation"), true); } /** * Attempts to instantiate the custom edit object delegate subclass, if one has been specified. */ private ERDEditObjectDelegate editObjectDelegateInstance() { ERDEditObjectDelegate delegate = null; String delegateClassName = (String)d2wContext().valueForKey("editObjectDelegateClass"); if (delegateClassName != null) { try { Class delegateClass = Class.forName(delegateClassName); Constructor delegateClassConstructor = delegateClass.getConstructor(WOContext.class); delegate = (ERDEditObjectDelegate)delegateClassConstructor.newInstance(context()); } catch (LinkageError le) { if (le instanceof ExceptionInInitializerError) { log.warn("Could not initialize edit object delegate class: " + delegateClassName); } else { log.warn("Could not load delegate class: " + delegateClassName + " due to: " + le.getMessage()); } } catch (ClassNotFoundException cnfe) { log.warn("Could not find class for edit object delegate: " + delegateClassName); } catch (NoSuchMethodException nsme) { log.warn("Could not find constructor for edit object delegate class: " + delegateClassName); } catch (SecurityException se) { log.warn("Insufficient privileges to access edit object delegate constructor: " + delegateClassName); } catch (IllegalAccessException iae) { log.warn("Insufficient access to create edit object delegate instance: " + iae.getMessage()); } catch (IllegalArgumentException iae) { log.warn("Used an illegal argument when creating edit object delegate instance: " + iae.getMessage()); } catch (InstantiationException ie) { log.warn("Could not instantiate edit object delegate instance: " + ie.getMessage()); } catch (InvocationTargetException ite) { log.warn("Exception while invoking edit object delegate constructor: " + ite.getMessage()); } } return delegate; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector<Void>("editingContextDidSaveChanges", ERXConstant.NotificationClassArray), EOEditingContext.EditingContextDidSaveChangesNotification, null); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); NSNotificationCenter.defaultCenter().removeObserver(this, EOEditingContext.EditingContextDidSaveChangesNotification, null); } }