diff --git a/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.html b/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.html new file mode 100644 index 00000000000..b32471087d6 --- /dev/null +++ b/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.html @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.wod b/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.wod new file mode 100644 index 00000000000..0e8520d13db --- /dev/null +++ b/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.wod @@ -0,0 +1,8 @@ +AjaxUpdateContainer: AjaxUpdateContainer { + id = id; +} + +AjaxUpdateTrigger: AjaxUpdateTrigger { + updateContainerIDs = updateContainerIDs; + resetAfterUpdate = true; +} \ No newline at end of file diff --git a/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.woo b/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.woo new file mode 100644 index 00000000000..a076a051a7e --- /dev/null +++ b/Frameworks/D2W/ERModernDirectToWeb/Components/Nonlocalized.lproj/ERMDAjaxNotificationCenter.wo/ERMDAjaxNotificationCenter.woo @@ -0,0 +1,4 @@ +{ + "WebObjects Release" = "WebObjects 5.0"; + encoding = "UTF-8"; +} \ No newline at end of file diff --git a/Frameworks/D2W/ERModernDirectToWeb/Sources/er/modern/directtoweb/components/ERMDAjaxNotificationCenter.java b/Frameworks/D2W/ERModernDirectToWeb/Sources/er/modern/directtoweb/components/ERMDAjaxNotificationCenter.java new file mode 100644 index 00000000000..94bc48c909d --- /dev/null +++ b/Frameworks/D2W/ERModernDirectToWeb/Sources/er/modern/directtoweb/components/ERMDAjaxNotificationCenter.java @@ -0,0 +1,231 @@ +package er.modern.directtoweb.components; + +import org.apache.log4j.Logger; + +import com.webobjects.appserver.WOContext; +import com.webobjects.directtoweb.D2WContext; +import com.webobjects.foundation.NSArray; +import com.webobjects.foundation.NSDictionary; +import com.webobjects.foundation.NSMutableArray; +import com.webobjects.foundation.NSMutableDictionary; +import com.webobjects.foundation.NSNotification; +import com.webobjects.foundation.NSNotificationCenter; +import com.webobjects.foundation.NSSelector; + +import er.ajax.AjaxUpdateContainer; +import er.directtoweb.components.ERDCustomComponent; +import er.extensions.appserver.ERXWOContext; +import er.extensions.eof.ERXConstant; +import er.extensions.eof.ERXKey; +import er.extensions.foundation.ERXArrayUtilities; +import er.extensions.foundation.ERXStringUtilities; +import er.modern.directtoweb.components.buttons.ERMDDeleteButton; + +/** + * ERMDAjaxNotificationCenter makes it easy to observe properties for changes + * and update dependent property keys. You just specify a dependency structure + * via the propertyDependencies D2W key. It takes a dictionary with property + * keys to be observed as keys and an array of the dependents to be updated as + * the value. Example: + * + *
+ * 100 : ((task = 'create' or task = 'edit') and entity.name = 'Person') => propertyDependencies = {"isFemale" = ("salutation"); "dateOfBirth" = ("discount", "parentEmail"); } [com.webobjects.directtoweb.Assignment]
+ * 
+ * + * This will observe the property keys "isFemale" and "dateOfBirth". If + * "isFemale" changes, the "salutation" property will be updated. If + * "dateOfBirth" changes, the "discount" and "parentEmail" properties will be + * updated. You can then hide and show properties on the fly by using the + * displayVariant key: + * + *
+ * 100 : ((task = 'create' or task = 'edit') and entity.name = 'Person' and propertyKey = 'parentEmail' and object.isAdult = '1') => displayVariant = "omit" [com.webobjects.directtoweb.Assignment]
+ * 
+ * + * + * By default, ERMDAjaxNotificationCenter will be included in the + * aboveDisplayPropertyKeys repetition when propertyDependencies is not null. If + * you set aboveDisplayPropertyKeys yourself, you have to include the + * "ajaxNotificationCenter" property key. + * + * Unlike the original version by Ramsey, this implementation depends on + * ERMDInspectPageRepetition to insert AjaxObserveField and AjaxUpdateContainer + * components where required. + * + * @d2wKey ajaxNotificationCenter + * @d2wKey propertyDependencies + * + * @author rgurley@mac.com + * @author fpeters + * + */ +public class ERMDAjaxNotificationCenter extends ERDCustomComponent { + + private static final long serialVersionUID = 1L; + + public static final ERXKey AJAX_NOTIFICATION_CENTER_ID = new ERXKey( + "ajaxNotificationCenterID"); + + public static final ERXKey PROPERTY_OBSERVER_ID = new ERXKey( + "propertyObserverID"); + + public static final ERXKey PROPERTY_KEY = new ERXKey("propertyKey"); + + public static final ERXKey>> PROPERTY_DEPENDENCIES = new ERXKey>>( + "propertyDependencies"); + + public static final String PropertyChangedNotification = "PropertyChangedNotification"; + + public static final String RegisterPropertyObserverIDNotification = "RegisterPropertyObserverIDNotification"; + + @SuppressWarnings("rawtypes") + private NSSelector propertyChanged = new NSSelector("propertyChanged", + ERXConstant.NotificationClassArray); + + private String id; + + private NSMutableArray updateContainerIDs = new NSMutableArray(); + + private static final Logger log = Logger.getLogger(ERMDAjaxNotificationCenter.class); + + public String id() { + if (id == null) { + id = ERXWOContext.safeIdentifierName(context(), true); + AJAX_NOTIFICATION_CENTER_ID.takeValueInObject(id, d2wContext()); + } + return id; + } + + public ERMDAjaxNotificationCenter(WOContext context) { + super(context); + } + + public void setD2wContext(D2WContext context) { + if (context != null && !context.equals(d2wContext())) { + log.debug("Removing observers for old context"); + NSNotificationCenter.defaultCenter().removeObserver(this, + PropertyChangedNotification, null); + } + NSNotificationCenter.defaultCenter().addObserver(this, propertyChanged, + PropertyChangedNotification, context); + log.debug("Notifications registered for context: " + context); + super.setD2wContext(context); + } + + public NSMutableArray updateContainerIDs() { + log.debug("Updating container IDs: " + + updateContainerIDs.componentsJoinedByString(", ")); + return updateContainerIDs; + } + + public void propertyChanged(NSNotification n) { + log.debug("Property changed for property key: " + + PROPERTY_KEY.valueInObject(n.object())); + + NSArray updateProps = propertyChanged((D2WContext) n.object()); + if (updateProps != null && updateProps.count() > 0) { + + refreshRelationships(updateProps); + + // collect the corresponding update container IDs + NSMutableArray attributeLineUCs = new NSMutableArray(); + D2WContext c = (D2WContext) n.object(); + String pageConfiguration = (String) c.valueForKey("pageConfiguration"); + for (String aPropertyName : updateProps) { + String lineUC = pageConfiguration; + lineUC = lineUC.concat(ERXStringUtilities.capitalize(aPropertyName)); + lineUC = lineUC.concat("LineUC"); + attributeLineUCs.addObject(lineUC); + } + + ERXArrayUtilities.addObjectsFromArrayWithoutDuplicates(updateContainerIDs, + attributeLineUCs); + + // force update of notification center UC + AjaxUpdateContainer.safeUpdateContainerWithID(id, context()); + log.debug("Container ids to be updated: " + + updateContainerIDs.componentsJoinedByString(", ")); + } + } + + public NSDictionary> propertyDependencies(D2WContext context) { + NSDictionary> propertyDependencies = PROPERTY_DEPENDENCIES + .valueInObject(context); + return propertyDependencies; + } + + /** + * @param context + * The d2wContext of the changed property level component + * @return a list of property keys to be updated + */ + @SuppressWarnings("unchecked") + public NSArray propertyChanged(D2WContext context) { + String prop = context.propertyKey(); + NSArray dependants = NSArray.EmptyArray; + NSDictionary> propertyDependencies = PROPERTY_DEPENDENCIES + .valueInObject(context); + if (propertyDependencies.containsKey(prop)) { + dependants = (NSArray) propertyDependencies.valueForKey(prop); + } + return dependants; + } + + /** + * Sends out a notification to instances of + * {@link ERMODEditRelationshipPage} for any relationships that have to be + * updated, causing them to be refetched. + * + * @param updateProps + */ + private void refreshRelationships(NSArray updateProps) { + for (String aPropertyKey : updateProps) { + // TODO handle key paths to different entities + if (d2wContext().entity().relationshipNamed(aPropertyKey) != null) { + // this is a relationship, so we'll send out a notification + Object obj = context().page(); + String OBJECT_KEY = "object"; + NSMutableDictionary userInfo = new NSMutableDictionary( + obj, OBJECT_KEY); + userInfo.setObjectForKey(d2wContext().valueForKey("object"), OBJECT_KEY); + userInfo.setObjectForKey(aPropertyKey, "propertyKey"); + userInfo.setObjectForKey(id, "ajaxNotificationCenterId"); + // HACK: the delete action notification is the only way to + // trigger a relationship component update for now + NSNotificationCenter.defaultCenter().postNotification( + ERMDDeleteButton.BUTTON_PERFORMED_DELETE_ACTION, obj, userInfo); + log.debug("Sent update notification for relationship: " + aPropertyKey); + } + } + } + + /** + * Since this component uses synchronization to update observers when the + * d2wContext changes, it cannot be non-synchronizing. However, if we want + * to be able to drop this component anywhere, it needs to be able to accept + * any binding value. So this method simply returns value for key from the + * dynamicBindings dictionary. + */ + public Object handleQueryWithUnboundKey(String key) { + if (log.isDebugEnabled()) { + log.debug("Handling unbound key: " + key); + } + return dynamicBindings().objectForKey(key); + } + + /** + * Since this component uses synchronization to update observers when the + * d2wContext changes, it cannot be non-synchronizing. However, if we want + * to be able to drop this component anywhere, it needs to be able to accept + * any binding value. So this method simply adds value for key to the + * dynamicBindings dictionary. + */ + @SuppressWarnings("unchecked") + public void handleTakeValueForUnboundKey(Object value, String key) { + if (log.isDebugEnabled()) { + log.debug("Take value: " + value + " for unbound key: " + key); + } + dynamicBindings().setObjectForKey(value, key); + } + +}