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);
+ }
+
+}