diff --git a/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.html b/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.html index e025570a5d4..8fabe718cee 100644 --- a/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.html +++ b/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.html @@ -56,6 +56,7 @@
  • Ajax Updates
  • Container in Container
  • Update Trigger
  • +
  • Multiple container updates
  • Autocompletion
  • Dependent Popups
  • Slider
  • diff --git a/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.wod b/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.wod index 83d0c8558fc..758874c084e 100644 --- a/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.wod +++ b/Examples/Ajax/AjaxExample/Components/AjaxExampleComponent.wo/AjaxExampleComponent.wod @@ -14,6 +14,10 @@ UpdateTriggerLink : WOHyperlink { pageName = "UpdateTriggerExample"; } +MultiUpdateExample : WOHyperlink { + pageName = "MultiUpdateExample"; +} + AutocompletionLink : WOHyperlink { pageName = "AutoCompleteExample"; } diff --git a/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.html b/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.html new file mode 100644 index 00000000000..bdb5ccca0d8 --- /dev/null +++ b/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.html @@ -0,0 +1,32 @@ + +

    + This example demonstrates the AjaxTrigger component, which allows you to enqueue a series of updates to + occur. Note that these happen in series as multiple requests, not as a single request, so you will want + to limit the number of updates you do in a single pass. +

    + + +

    Container 1

    + + + + , and also update: +

    + Container 2 +

    +

    + Container 3 +

    +
    +
    + + +

    Container 2

    + + + + +

    Container 3

    + + +
    \ No newline at end of file diff --git a/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.wod b/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.wod new file mode 100644 index 00000000000..e89450bf8c9 --- /dev/null +++ b/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.wod @@ -0,0 +1,47 @@ +AjaxExampleComponent : AjaxExampleComponent { + title = "Update Trigger"; +} + +AjaxUpdateContainer : AjaxUpdateContainer { + id = "container1"; + style = "background: green; color: white; padding: 10px"; +} + +Now : WOString { + value = now; +} + +WOForm : WOForm { + multipleSubmit = true; +} + +AjaxSubmitButton : AjaxSubmitButton { + updateContainerID = containerIdsToUpdate; + value = "Update Container 1"; +} + +WOCheckBox : WOCheckBox { + checked = updateContainer2; +} + +WOCheckBox1 : WOCheckBox { + checked = updateContainer3; +} + +AjaxUpdateContainer1 : AjaxUpdateContainer { + id = "container2"; + style = "background: blue; color: white; padding: 10px"; +} + +Now1 : WOString { + value = now; +} + +AjaxUpdateContainer2 : AjaxUpdateContainer { + id = "container3"; + style = "background: red; color: white; padding: 10px"; +} + +Now2 : WOString { + value = now; +} diff --git a/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.woo b/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.woo new file mode 100644 index 00000000000..a076a051a7e --- /dev/null +++ b/Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.woo @@ -0,0 +1,4 @@ +{ + "WebObjects Release" = "WebObjects 5.0"; + encoding = "UTF-8"; +} \ No newline at end of file diff --git a/Examples/Ajax/AjaxExample/Components/UpdateTriggerExample.wo/UpdateTriggerExample.html b/Examples/Ajax/AjaxExample/Components/UpdateTriggerExample.wo/UpdateTriggerExample.html index 76793a85ee5..d400957d473 100644 --- a/Examples/Ajax/AjaxExample/Components/UpdateTriggerExample.wo/UpdateTriggerExample.html +++ b/Examples/Ajax/AjaxExample/Components/UpdateTriggerExample.wo/UpdateTriggerExample.html @@ -1,8 +1,8 @@

    - This example demonstrates the AjaxTrigger component, which allows you to enqueue a series of updates to - occur. Note that these happen in series as multiple requests, not as a single request, so you will want - to limit the number of updates you do in a single pass. + AjaxTrigger gives you multiple container updates in one series. This exmaple shows, how you can + achieve the same result in one RR cycle by supplying a NSArray of container ids (alternatively a + comma separate string with multiple container ids). You see from the timestamps that all updates are generated at exactly the same time.

    diff --git a/Examples/Ajax/AjaxExample/Sources/MultiUpdateExample.java b/Examples/Ajax/AjaxExample/Sources/MultiUpdateExample.java new file mode 100644 index 00000000000..79c6a79c8bf --- /dev/null +++ b/Examples/Ajax/AjaxExample/Sources/MultiUpdateExample.java @@ -0,0 +1,56 @@ +import com.webobjects.appserver.WOComponent; +import com.webobjects.appserver.WOContext; +import com.webobjects.foundation.NSArray; +import com.webobjects.foundation.NSMutableArray; + +public class MultiUpdateExample extends WOComponent { + + private NSMutableArray _updateContainerIDs = new NSMutableArray<>("container1"); + private long now; + + public MultiUpdateExample(WOContext context) { + super(context); + } + + @Override + public void awake() { + now = System.currentTimeMillis(); + + super.awake(); + } + + public long now() { + return now; + } + + public NSArray containerIdsToUpdate() { + return _updateContainerIDs; + } + + public void setUpdateContainer2(boolean updateContainer2) { + setUpdateContainer("container2", updateContainer2); + } + + public boolean updateContainer2() { + return _updateContainerIDs.containsObject("container2"); + } + + public void setUpdateContainer3(boolean updateContainer3) { + setUpdateContainer("container3", updateContainer3); + } + + public boolean updateContainer3() { + return _updateContainerIDs.containsObject("container3"); + } + + protected void setUpdateContainer(String id, boolean updateContainer3) { + if (updateContainer3) { + if (!_updateContainerIDs.containsObject(id)) { + _updateContainerIDs.addObject(id); + } + } + else { + _updateContainerIDs.removeObject(id); + } + } +} diff --git a/Examples/Ajax/AjaxExample/Sources/UpdateTriggerExample.java b/Examples/Ajax/AjaxExample/Sources/UpdateTriggerExample.java index 98812e9ef93..8ded5b616e5 100644 --- a/Examples/Ajax/AjaxExample/Sources/UpdateTriggerExample.java +++ b/Examples/Ajax/AjaxExample/Sources/UpdateTriggerExample.java @@ -2,18 +2,25 @@ import com.webobjects.appserver.WOContext; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSMutableArray; -import com.webobjects.foundation.NSTimestamp; public class UpdateTriggerExample extends WOComponent { private NSMutableArray _updateContainerIDs = new NSMutableArray<>(); + private long now; public UpdateTriggerExample(WOContext context) { super(context); } - public NSTimestamp now() { - return new NSTimestamp(); + @Override + public void awake() { + now = System.currentTimeMillis(); + + super.awake(); + } + + public long now() { + return now; } public NSArray otherIDsToUpdate() { diff --git a/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api b/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api index 11ac5ffa889..d8c99f009fb 100644 --- a/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api +++ b/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api @@ -18,5 +18,6 @@ + \ No newline at end of file diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java index 9e2af466e17..43889241e7f 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java @@ -62,7 +62,8 @@ * @binding style CSS style to use on the container. (Only used if you leave off observeFieldID) * @binding onCreate Takes a JavaScript function which is called after the form has been serialized, * but before the Ajax request is sent to the server. Useful e.g. if you want to disable the - * form while the Ajax request is running. + * form while the ajax request is running. + * @binding actOnInput When true, input events in text input fields lead to an immediate action */ public class AjaxObserveField extends AjaxDynamicElement { public AjaxObserveField(String name, NSDictionary associations, WOElement children) { @@ -102,6 +103,7 @@ public void appendToResponse(WOResponse response, WOContext context) { String updateContainerID = AjaxUpdateContainer.updateContainerID(this, component); NSMutableDictionary options = createAjaxOptions(component); boolean fullSubmit = booleanValueForBinding("fullSubmit", false, component); + boolean actOnInput = booleanValueForBinding("actOnInput", false, component); boolean observeFieldDescendents; if (observeFieldID != null) { observeFieldDescendents = false; @@ -125,11 +127,14 @@ public void appendToResponse(WOResponse response, WOContext context) { response.appendContentString(""); } AjaxUtils.appendScriptHeader(response); - AjaxObserveField.appendToResponse(response, context, this, observeFieldID, observeFieldDescendents, updateContainerID, fullSubmit, options); + AjaxObserveField.appendToResponse(response, context, this, observeFieldID, observeFieldDescendents, updateContainerID, fullSubmit, options, actOnInput); AjaxUtils.appendScriptFooter(response); } - public static void appendToResponse(WOResponse response, WOContext context, AjaxDynamicElement element, String observeFieldID, boolean observeDescendentFields, String updateContainerID, boolean fullSubmit, NSMutableDictionary options) { + public static void appendToResponse(WOResponse response, WOContext context, AjaxDynamicElement element, String observeFieldID, boolean observeDescendentFields, String updateContainerID, boolean fullSubmit, NSMutableDictionary options) { + appendToResponse(response, context, element, observeFieldID, observeDescendentFields, updateContainerID, fullSubmit, options, false); + } + public static void appendToResponse(WOResponse response, WOContext context, AjaxDynamicElement element, String observeFieldID, boolean observeDescendentFields, String updateContainerID, boolean fullSubmit, NSMutableDictionary options, boolean actOnInput) { WOComponent component = context.component(); String submitButtonName = nameInContext(context, component, element); NSMutableDictionary observerOptions = new NSMutableDictionary<>(); @@ -158,7 +163,7 @@ public static void appendToResponse(WOResponse response, WOContext context, Ajax response.appendContentString(observeDelay); response.appendContentString(", "); AjaxOptions.appendToResponse(observerOptions, response, context); - response.appendContentString(");"); + response.appendContentString(", " + actOnInput +");"); } public static String nameInContext(WOContext context, WOComponent component, AjaxDynamicElement element) { diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java index 8a0221c94e9..4ef2446c8a2 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java @@ -1,7 +1,9 @@ package er.ajax; import java.util.Enumeration; +import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,11 +14,13 @@ import com.webobjects.appserver.WOResponse; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; +import com.webobjects.foundation.NSRange; import er.extensions.appserver.ERXResponse; import er.extensions.appserver.ERXWOContext; import er.extensions.appserver.ajax.ERXAjaxApplication; import er.extensions.appserver.ajax.ERXAjaxApplication.ERXAjaxResponseDelegate; +import er.extensions.appserver.ajax.ERXAjaxSession; /** * AjaxResponse provides support for performing an AjaxUpdate in the same response @@ -68,7 +72,7 @@ public WOResponse generateResponse() { _content = new StringBuilder(); NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); userInfo.setObjectForKey(Boolean.TRUE, AjaxResponse.AJAX_UPDATE_PASS); - WOActionResults woactionresults = WOApplication.application().invokeAction(_request, _context); + WOApplication.application().invokeAction(_request, _context); _content.append(originalContent); if (_responseAppenders != null) { Enumeration responseAppendersEnum = _responseAppenders.objectEnumerator(); @@ -77,17 +81,74 @@ public WOResponse generateResponse() { responseAppender.appendToResponse(this, _context); } } - if (_contentLength() == 0) { + + int length = ((CharSequence)_content).length(); + if (length == 0) { setStatus(HTTP_STATUS_INTERNAL_ERROR); log.warn("You performed an Ajax update, but no response was generated. A common cause of this is that you spelled your updateContainerID wrong. You specified a container ID '" + AjaxUpdateContainer.updateContainerID(_request) + "'."); } + } finally { _context._setSenderID(originalSenderID); } + } else + { + List updateContainerIDList = AjaxUpdateContainer.updateContainerIDList(_context.request()); + + if(updateContainerIDList != null) + { + _context.request().setHeader(StringUtils.join(updateContainerIDList, ','), ERXAjaxSession.PAGE_REPLACEMENT_CACHE_LOOKUP_KEY); + + WOApplication.application().appendToResponse(this, _context); + + StringBuilder c2 = new StringBuilder(); + boolean firstUC = true; + + for(String id : updateContainerIDList) + { + NSRange r = AjaxUpdateContainer.rangeForContainerID(_request, id); + + if(r != null) + { + StringBuilder c = new StringBuilder(_content.substring(r.location(), r.location()+r.length())); + fixLeadingWhiteSpaces(c); + + if(firstUC) + { + c2.append(c); + firstUC = false; + } else + { + c2.append("\n\n"); + } + } + } + + _content = c2; + } } + + if(isHTML()) + fixLeadingWhiteSpaces(_content); + return this; } + + public int contentLength() + { + return _content.length(); + } + + // Some older Browsers do have problems with leading white space characters in Ajax Responses + // so we remove them on HTML responses + private void fixLeadingWhiteSpaces(StringBuilder sb) + { + while(sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) + sb.deleteCharAt(0); + } public static boolean isAjaxUpdatePass(WORequest request) { return ERXWOContext.contextDictionary().valueForKey(AjaxResponse.AJAX_UPDATE_PASS) != null; @@ -153,32 +214,22 @@ public void appendScriptFooterIfNecessary() { } /** - * Convenience method that calls AjaxUtils.updateDomElement with this request. - * - * @param id - * ID of the DOM element to update - * @param value - * The new value - * @param numberFormat - * optional number format to format the value with - * @param dateFormat - * optional date format to format the value with - * @param valueWhenEmpty - * string to use when value is null - * - * @see er.ajax.AjaxUtils#updateDomElement(WOResponse, String, Object, String, String, String) + * Convenience method that calls AjaxUtils.updateDomElement with this request. + * @param id + * @param value + * @param numberFormat + * @param dateFormat + * @param valueWhenEmpty + * @see AjaxUtils#updateDomElement */ public void updateDomElement(String id, Object value, String numberFormat, String dateFormat, String valueWhenEmpty) { AjaxUtils.updateDomElement(this, id, value, numberFormat, dateFormat, valueWhenEmpty); } /** - * Convenience method that calls updateDomElement with no formatters and no valueWhenEmpty string. - * - * @param id - * ID of the DOM element to update - * @param value - * The new value + * Convenience method that calls updateDomElement with no formatters and no valueWhenEmpty string. + * @param id + * @param value */ public void updateDomElement(String id, Object value) { updateDomElement(id, value, null, null, null); diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java index 0f62f2f71c3..4f49876b805 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java @@ -1,5 +1,8 @@ package er.ajax; +import java.util.Arrays; +import java.util.List; + import com.webobjects.appserver.WOActionResults; import com.webobjects.appserver.WOAssociation; import com.webobjects.appserver.WOComponent; @@ -10,6 +13,7 @@ import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; +import com.webobjects.foundation.NSRange; import er.extensions.appserver.ERXWOContext; import er.extensions.appserver.ajax.ERXAjaxApplication; @@ -29,6 +33,7 @@ * @binding optional set to true if you want the container tags to be skipped if this is already in an update container (similar to ERXOptionalForm). * If optional is true and there is a container, it's as if this AUC doesn't exist, and only its children will render to the page. * + * @binding disabled set to true if you do not want the update container to be rendered at all (defaults to false) * @binding frequency the frequency (in seconds) of a periodic update * @binding decay a multiplier (default is one) applied to the frequency if the response of the update is unchanged * @binding stopped determines whether a periodic update container loads as stopped. @@ -45,13 +50,22 @@ public AjaxUpdateContainer(String name, NSDictionary asso */ @Override protected void addRequiredWebResources(WOResponse response, WOContext context) { - addScriptResourceInHead(context, response, "prototype.js"); - addScriptResourceInHead(context, response, "effects.js"); - addScriptResourceInHead(context, response, "wonder.js"); + if (shouldRenderContainer(context.component())) { + addScriptResourceInHead(context, response, "prototype.js"); + addScriptResourceInHead(context, response, "effects.js"); + addScriptResourceInHead(context, response, "wonder.js"); + } } protected boolean shouldRenderContainer(WOComponent component) { - boolean renderContainer = !booleanValueForBinding("optional", false, component) || AjaxUpdateContainer.currentUpdateContainerID() == null; + boolean renderContainer = false; + if(!booleanValueForBinding("disabled", false, component)) + { + // if 'disabled' == false: old behaviour: + renderContainer = + !booleanValueForBinding("optional", false, component) || + AjaxUpdateContainer.currentUpdateContainerID() == null; + } return renderContainer; } @@ -166,6 +180,10 @@ public NSMutableDictionary createObserveFieldOptions(WOComponent component) { @Override public void appendToResponse(WOResponse response, WOContext context) { WOComponent component = context.component(); + List updateContainerIDList = updateContainerIDList(context.request()); + boolean markContentRange = response instanceof AjaxResponse && updateContainerIDList != null && updateContainerIDList.contains(valueForBinding("id", component)); + int contentLength1 = 0; + if (!shouldRenderContainer(component)) { if (hasChildrenElements()) { appendChildrenToResponse(response, context); @@ -185,9 +203,17 @@ public void appendToResponse(WOResponse response, WOContext context) { appendTagAttributeToResponse(response, "data-updateUrl", AjaxUtils.ajaxComponentActionUrl(context)); // appendTagAttributeToResponse(response, "woElementID", context.elementID()); response.appendContentString(">"); + + if(markContentRange) + contentLength1 = ((AjaxResponse)response).contentLength(); + if (hasChildrenElements()) { appendChildrenToResponse(response, context); } + + if(markContentRange) + setRangeForContainerID(context.request(), (String) valueForBinding("id", component), new NSRange(contentLength1, ((AjaxResponse)response).contentLength() - contentLength1)); + response.appendContentString(""); super.appendToResponse(response, context); @@ -230,7 +256,8 @@ public void appendToResponse(WOResponse response, WOContext context) { if (observeFieldID != null) { boolean fullSubmit = booleanValueForBinding("fullSubmit", false, component); - AjaxObserveField.appendToResponse(response, context, this, observeFieldID, false, id, fullSubmit, createObserveFieldOptions(component)); + boolean actOnInput = booleanValueForBinding("actOnInput", false, component); + AjaxObserveField.appendToResponse(response, context, this, observeFieldID, false, id, fullSubmit, createObserveFieldOptions(component), actOnInput); } response.appendContentString("AUC.register('" + id + "'"); @@ -272,6 +299,7 @@ public WOActionResults handleRequest(WORequest request, WOContext context) { response.appendContentString(onRefreshComplete); AjaxUtils.appendScriptFooter(response); } + if (AjaxModalDialog.isInDialog(context)) { AjaxUtils.appendScriptHeader(response); response.appendContentString("AMD.contentUpdated();"); @@ -295,9 +323,45 @@ public static String updateContainerID(WORequest request) { public static void setUpdateContainerID(WORequest request, String updateContainerID) { if (updateContainerID != null) { - ERXWOContext.contextDictionary().setObjectForKey(updateContainerID, ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID); + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); + + if(updateContainerID.indexOf(",") >= 0) + userInfo.setObjectForKey(Arrays.asList(updateContainerID.split(",")), CONTAINER_LIST_KEY); + else + userInfo.setObjectForKey(updateContainerID, ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID); + } + } + + public static List updateContainerIDList(WORequest request) + { + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); + return (List) userInfo.objectForKey(CONTAINER_LIST_KEY); + } + + public static void setRangeForContainerID(WORequest request, String updateContainerID, NSRange range) + { + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); + NSMutableDictionary rangeDict = (NSMutableDictionary) userInfo.objectForKey(CONTAINER_RANGEDICT_KEY); + if(rangeDict == null) + { + rangeDict = new NSMutableDictionary(); + userInfo.setObjectForKey(rangeDict, CONTAINER_RANGEDICT_KEY); } + + rangeDict.setObjectForKey(range, updateContainerID); } + + public static NSRange rangeForContainerID(WORequest request, String updateContainerID) + { + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); + NSMutableDictionary rangeDict = (NSMutableDictionary) userInfo.objectForKey(CONTAINER_RANGEDICT_KEY); + if(rangeDict != null) + return rangeDict.objectForKey(updateContainerID); + return null; + } + + private static final String CONTAINER_LIST_KEY = "_ul"; + private static final String CONTAINER_RANGEDICT_KEY = "_rd"; public static boolean hasUpdateContainerID(WORequest request) { return AjaxUpdateContainer.updateContainerID(request) != null; @@ -321,7 +385,25 @@ public static String updateContainerID(AjaxDynamicElement element, WOComponent c } public static String updateContainerID(AjaxDynamicElement element, String bindingName, WOComponent component) { - String updateContainerID = (String) element.valueForBinding("updateContainerID", component); + Object valueForBinding = element.valueForBinding("updateContainerID", component); + String updateContainerID = null; + + if(valueForBinding instanceof String) + updateContainerID = (String) valueForBinding; + if(valueForBinding instanceof List) + { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for(String s : (List)valueForBinding) + { + if(!first) + sb.append(','); + else + first = false; + sb.append(s); + } + updateContainerID = sb.toString(); + } return AjaxUpdateContainer.updateContainerID(updateContainerID); } diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateTrigger.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateTrigger.java index 10cb40435a4..6a4c8a6b0dc 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateTrigger.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateTrigger.java @@ -9,6 +9,7 @@ import com.webobjects.appserver.WODynamicElement; import com.webobjects.appserver.WOElement; import com.webobjects.appserver.WOResponse; +import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import er.extensions.components.ERXComponentUtilities; @@ -23,7 +24,7 @@ * edit mode and trigger all of the other update containers to update, * reflecting their new non-editable status. * - * @binding updateContainerIDs an array of update container IDs to update + * @binding updateContainerIDs an array of update container IDs to update / or a comma separated string of container IDs * @binding resetAfterUpdate if true, the array of IDs will be cleared after appendToResponse * * @author mschrag @@ -44,7 +45,7 @@ public AjaxUpdateTrigger(String name, NSDictionary associ public void appendToResponse(WOResponse response, WOContext context) { super.appendToResponse(response, context); WOComponent component = context.component(); - List updateContainerIDs = (List) _updateContainerIDs.valueInComponent(component); + List updateContainerIDs = getUpdateContainerIds(component); if (updateContainerIDs != null && updateContainerIDs.size() > 0) { AjaxUtils.appendScriptHeader(response); Iterator updateContainerIDEnum = updateContainerIDs.iterator(); @@ -64,4 +65,22 @@ public void appendToResponse(WOResponse response, WOContext context) { } } + @SuppressWarnings("unchecked") + private List getUpdateContainerIds(WOComponent component) + { + Object value = _updateContainerIDs.valueInComponent(component); + + if(value instanceof List) + { + return (List) value; + } + + if(value instanceof String) + { + return new NSArray(((String)value).split(",")); + } + + throw new IllegalArgumentException("Invalid argument for 'updateContainerIDs' given"); + } + } diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java index 16ed6eddfd4..e1a33c0bacd 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java @@ -32,6 +32,26 @@ public static String javaScriptEscaped(Object obj) { escapedValue = "'" + escapedValue + "'"; return escapedValue; } + + public static String replaceCRLF(Object obj) { + String escapedValue = String.valueOf(obj); + escapedValue = escapedValue.replaceAll("\r", "\\\\r"); + escapedValue = escapedValue.replaceAll("\n", "\\\\n"); + return escapedValue; + } + + public static String javaScriptAndHTMLEscaped(Object obj) + { + String escapedValue = String.valueOf(obj); + escapedValue = escapedValue.replaceAll("\\\\", "\\\\\\\\"); + escapedValue = escapedValue.replaceAll("<", "\\\\x3C"); + escapedValue = escapedValue.replaceAll(">", "\\\\x3E"); + escapedValue = escapedValue.replaceAll("\r", "\\\\r"); + escapedValue = escapedValue.replaceAll("\n", "\\\\n"); + escapedValue = escapedValue.replaceAll("'", "\\\\'"); + escapedValue = "'" + escapedValue + "'"; + return escapedValue; + } /** * Creates AjaxValue for value with the type guessed at. diff --git a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js index 0a9d853ce51..ded85cba8db 100644 --- a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js +++ b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js @@ -338,14 +338,21 @@ var AjaxUpdateLink = { }, update: function(id, options, elementID, queryParams) { - var updateElement = $(id); + var firstId = id; + var updateElementList = id.split(","); + if(updateElementList.length > 1) + { + firstId = updateElementList[0]; + } + + var updateElement = $(firstId); if (updateElement == null) { alert('There is no element on this page with the id "' + id + '".'); } - AjaxUpdateLink._update(id, updateElement.getAttribute('data-updateUrl'), options, elementID, queryParams); + AjaxUpdateLink._update(id, firstId, updateElement.getAttribute('data-updateUrl'), options, elementID, queryParams); }, - _update: function(id, actionUrl, options, elementID, queryParams) { + _update: function(id, firstId, actionUrl, options, elementID, queryParams) { if (elementID) { actionUrl = actionUrl.sub(/[^\/]+$/, elementID); } @@ -354,10 +361,17 @@ var AjaxUpdateLink = { actionUrl = actionUrl.addQueryParameters('_r='+ id); } else { - actionUrl = actionUrl.addQueryParameters('_u='+ id); + if(id.indexOf(",") >= 0) + { + actionUrl = actionUrl.addQueryParameters('_ul='+ id); + } + else + { + actionUrl = actionUrl.addQueryParameters('_u='+ id); + } } actionUrl = actionUrl.addQueryParameters(new Date().getTime()); - new Ajax.Updater(id, actionUrl, AjaxOptions.defaultOptions(options)); + new Ajax.Updater(firstId, actionUrl, AjaxOptions.defaultOptions(options)); }, request: function(actionUrl, options, elementID, queryParams) { @@ -392,7 +406,10 @@ var AjaxSubmitButton = { actionUrl = actionUrl.addQueryParameters('_r=' + id); } else { - actionUrl = actionUrl.addQueryParameters('_u=' + id); + if(id.indexOf(",") >= 0) + actionUrl = actionUrl.addQueryParameters('_ul=' + id); + else + actionUrl = actionUrl.addQueryParameters('_u=' + id); } } actionUrl = actionUrl.addQueryParameters(new Date().getTime()); @@ -448,13 +465,17 @@ var AjaxSubmitButton = { }, update: function(id, form, queryParams, options) { - var updateElement = $(id); + var firstId = id; + var updateElementlist = id.split(","); + if(updateElementlist.length > 1) + firstId = updateElementlist[0]; + var updateElement = $(firstId); if (updateElement == null) { alert('There is no element on this page with the id "' + id + '".'); } var finalUrl = AjaxSubmitButton.generateActionUrl(id, form, queryParams, options); var finalOptions = AjaxSubmitButton.processOptions(form, options); - new Ajax.Updater(id, finalUrl, finalOptions); + new Ajax.Updater(firstId, finalUrl, finalOptions); }, request: function(form, queryParams, options) { @@ -463,15 +484,15 @@ var AjaxSubmitButton = { new Ajax.Request(finalUrl, finalOptions); }, - observeDescendentFields: function(updateContainerID, containerID, observeFieldFrequency, partial, observeDelay, options) { + observeDescendentFields: function(updateContainerID, containerID, observeFieldFrequency, partial, observeDelay, options, actOnInput) { $(containerID).descendants().find(function(element) { if (element.type != 'hidden' && ['input', 'select', 'textarea'].include(element.tagName.toLowerCase())) { - AjaxSubmitButton.observeField(updateContainerID, element, observeFieldFrequency, partial, observeDelay, options); + AjaxSubmitButton.observeField(updateContainerID, element, observeFieldFrequency, partial, observeDelay, options, actOnInput); } }); }, - observeField: function(updateContainerID, formFieldID, observeFieldFrequency, partial, observeDelay, options) { + observeField: function(updateContainerID, formFieldID, observeFieldFrequency, partial, observeDelay, options, actOnInput) { var submitFunction; if (partial) { // We need to cheat and make the WOForm that contains the form action appear to have been @@ -510,7 +531,7 @@ var AjaxSubmitButton = { new Form.Element.RadioButtonObserver($(formFieldID), submitFunction); } else { - new Form.Element.EventObserver($(formFieldID), submitFunction); + new Form.Element.ExtendedEventObserver($(formFieldID), submitFunction, actOnInput); } } else { @@ -978,6 +999,37 @@ Form.Element.RadioButtonObserver = Class.create(Form.Element.EventObserver, { } }); +Form.Element.ExtendedEventObserver = Class.create(Form.Element.EventObserver, { + initialize: function($super, element, callback, actOnInput) { + this.actOnInput = actOnInput; + $super(element, callback); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'text': + case 'number': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + if (this.actOnInput) + { + Event.observe(element, 'input', this.onElementEvent.bind(this)); + Event.observe(element, 'blur', this.onElementEvent.bind(this)); + } + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } + +}); + var AjaxBusy = { spinners: {},