From aa34ae5855b58796dbc5be71c19feb9701b73223 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Wed, 13 Feb 2013 20:28:43 +0100 Subject: [PATCH 01/15] AjaxObservceField: new option to act "onkeyup" --- .../Sources/er/ajax/AjaxObserveField.java | 10 +++--- .../Sources/er/ajax/AjaxUpdateContainer.java | 3 +- .../Ajax/Ajax/WebServerResources/wonder.js | 35 ++++++++++++++++--- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java index 9e2af466e17..ef3c3b172a3 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 actOnKeyUp When true, keyup 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 actOnKeyUp = booleanValueForBinding("actOnKeyUp", false, component); boolean observeFieldDescendents; if (observeFieldID != null) { observeFieldDescendents = false; @@ -125,11 +127,11 @@ 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, actOnKeyUp); 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, boolean actOnKeyUp) { WOComponent component = context.component(); String submitButtonName = nameInContext(context, component, element); NSMutableDictionary observerOptions = new NSMutableDictionary<>(); @@ -158,7 +160,7 @@ public static void appendToResponse(WOResponse response, WOContext context, Ajax response.appendContentString(observeDelay); response.appendContentString(", "); AjaxOptions.appendToResponse(observerOptions, response, context); - response.appendContentString(");"); + response.appendContentString(", " + actOnKeyUp +");"); } public static String nameInContext(WOContext context, WOComponent component, AjaxDynamicElement element) { diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java index 0f62f2f71c3..03bf5487111 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java @@ -230,7 +230,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 actOnKeyUp = booleanValueForBinding("actOnKeyUp", false, component); + AjaxObserveField.appendToResponse(response, context, this, observeFieldID, false, id, fullSubmit, createObserveFieldOptions(component), actOnKeyUp); } response.appendContentString("AUC.register('" + id + "'"); diff --git a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js index 0a9d853ce51..a64ff4e87e2 100644 --- a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js +++ b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js @@ -463,15 +463,15 @@ var AjaxSubmitButton = { new Ajax.Request(finalUrl, finalOptions); }, - observeDescendentFields: function(updateContainerID, containerID, observeFieldFrequency, partial, observeDelay, options) { + observeDescendentFields: function(updateContainerID, containerID, observeFieldFrequency, partial, observeDelay, options, actOnKeyUp) { $(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, actOnKeyUp); } }); }, - observeField: function(updateContainerID, formFieldID, observeFieldFrequency, partial, observeDelay, options) { + observeField: function(updateContainerID, formFieldID, observeFieldFrequency, partial, observeDelay, options, actOnKeyUp) { var submitFunction; if (partial) { // We need to cheat and make the WOForm that contains the form action appear to have been @@ -510,7 +510,7 @@ var AjaxSubmitButton = { new Form.Element.RadioButtonObserver($(formFieldID), submitFunction); } else { - new Form.Element.EventObserver($(formFieldID), submitFunction); + new Form.Element.ExtendedEventObserver($(formFieldID), submitFunction, actOnKeyUp); } } else { @@ -978,6 +978,33 @@ Form.Element.RadioButtonObserver = Class.create(Form.Element.EventObserver, { } }); +Form.Element.ExtendedEventObserver = Class.create(Form.Element.EventObserver, { + initialize: function($super, element, callback, actOnKeyUp) { + this.actOnKeyUp = actOnKeyUp; + $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': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + if (this.actOnKeyUp) + Event.observe(element, 'keyup', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } + +}); + var AjaxBusy = { spinners: {}, From 20f8cd5ea0080f9014708704af487b486c3fc107 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Wed, 13 Feb 2013 20:38:23 +0100 Subject: [PATCH 02/15] AjaxUpdateContainer: enable multiple Updates at once within a RR-cycle (e.g. triggered from an AjaxObserveField) --- .../Ajax/Sources/er/ajax/AjaxResponse.java | 85 +++++++++++++----- .../Sources/er/ajax/AjaxUpdateContainer.java | 90 +++++++++++++++++-- .../Ajax/Ajax/Sources/er/ajax/AjaxValue.java | 20 +++++ .../Ajax/Ajax/WebServerResources/wonder.js | 13 ++- 4 files changed, 176 insertions(+), 32 deletions(-) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java index 8a0221c94e9..13f8a14fcfd 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java @@ -1,6 +1,7 @@ package er.ajax; import java.util.Enumeration; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,6 +13,7 @@ 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; @@ -58,7 +60,7 @@ public AjaxResponse(WORequest request, WOContext context) { _context = context; } - @Override + @SuppressWarnings("cast") public WOResponse generateResponse() { if (AjaxUpdateContainer.hasUpdateContainerID(_request)) { String originalSenderID = _context.senderID(); @@ -68,7 +70,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 +79,64 @@ 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) + { + 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"); + } + } + } + + if (((Object)_content) instanceof StringBuffer) + ERXKeyValueCodingUtilities.takePrivateValueForKey(this, new StringBuffer(c2), "_content"); + else + ERXKeyValueCodingUtilities.takePrivateValueForKey(this, c2, "_content"); + } } + return this; } + + public int contentLength() + { + return _content.length(); + } public static boolean isAjaxUpdatePass(WORequest request) { return ERXWOContext.contextDictionary().valueForKey(AjaxResponse.AJAX_UPDATE_PASS) != null; @@ -153,32 +202,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 03bf5487111..f208b316f0d 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); @@ -273,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();"); @@ -296,9 +323,43 @@ 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); + if(updateContainerID.indexOf(",") >= 0) + AjaxUtils.mutableUserInfo(request).setObjectForKey(Arrays.asList(updateContainerID.split(",")), CONTAINER_LIST_KEY); + else + AjaxUtils.mutableUserInfo(request).setObjectForKey(updateContainerID, ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID); } } + + public static List updateContainerIDList(WORequest request) + { + NSDictionary userInfo = AjaxUtils.mutableUserInfo(request); + return (List) userInfo.objectForKey(CONTAINER_LIST_KEY); + } + + public static void setRangeForContainerID(WORequest request, String updateContainerID, NSRange range) + { + NSMutableDictionary userInfo = AjaxUtils.mutableUserInfo(request); + 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 = AjaxUtils.mutableUserInfo(request); + 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; @@ -322,7 +383,24 @@ 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/AjaxValue.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java index 16ed6eddfd4..63e3fcb08ea 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 a64ff4e87e2..9c24de1cd85 100644 --- a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js +++ b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js @@ -392,7 +392,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 +451,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) { From 1906ac8dcde6909b167964b73aa7439ce337f875 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Wed, 13 Feb 2013 20:42:25 +0100 Subject: [PATCH 03/15] AjaxUpdateContainer: enable multiple Updates, Part 2 --- .../Ajax/Sources/er/ajax/AjaxResponse.java | 6 +-- .../Sources/er/ajax/AjaxUpdateContainer.java | 37 ++++++++++--------- .../Ajax/Ajax/Sources/er/ajax/AjaxValue.java | 26 ++++++------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java index 13f8a14fcfd..519194c4f33 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java @@ -108,7 +108,6 @@ public WOResponse generateResponse() { if(r != null) { StringBuilder c = new StringBuilder(_content.substring(r.location(), r.location()+r.length())); - fixLeadingWhiteSpaces(c); if(firstUC) { @@ -123,10 +122,7 @@ public WOResponse generateResponse() { } } - if (((Object)_content) instanceof StringBuffer) - ERXKeyValueCodingUtilities.takePrivateValueForKey(this, new StringBuffer(c2), "_content"); - else - ERXKeyValueCodingUtilities.takePrivateValueForKey(this, c2, "_content"); + _content = c2; } } diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java index f208b316f0d..1d8412532d1 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java @@ -383,24 +383,25 @@ public static String updateContainerID(AjaxDynamicElement element, WOComponent c } public static String updateContainerID(AjaxDynamicElement element, String bindingName, WOComponent 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(); -      } + 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/AjaxValue.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java index 63e3fcb08ea..e1a33c0bacd 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxValue.java @@ -34,23 +34,23 @@ public static String javaScriptEscaped(Object obj) { } public static String replaceCRLF(Object obj) { -      String escapedValue = String.valueOf(obj); -      escapedValue = escapedValue.replaceAll("\r", "\\\\r"); -      escapedValue = escapedValue.replaceAll("\n", "\\\\n"); -      return escapedValue; + 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; + 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; } /** From beda564d0017a2d1e5a34959fec0ba716368fff3 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Wed, 13 Feb 2013 20:46:21 +0100 Subject: [PATCH 04/15] AjaxResponse: remove leading whit spaces on html fragments, Opera browser has a bug that does stall on these leading w.s. --- .../Ajax/Ajax/Sources/er/ajax/AjaxResponse.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java index 519194c4f33..08909c3ebe7 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java @@ -14,7 +14,6 @@ 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; @@ -60,7 +59,7 @@ public AjaxResponse(WORequest request, WOContext context) { _context = context; } - @SuppressWarnings("cast") + @Override public WOResponse generateResponse() { if (AjaxUpdateContainer.hasUpdateContainerID(_request)) { String originalSenderID = _context.senderID(); @@ -108,7 +107,8 @@ public WOResponse generateResponse() { if(r != null) { StringBuilder c = new StringBuilder(_content.substring(r.location(), r.location()+r.length())); - + fixLeadingWhiteSpaces(c); + if(firstUC) { c2.append(c); @@ -126,6 +126,9 @@ public WOResponse generateResponse() { } } + if(isHTML()) + fixLeadingWhiteSpaces(_content); + return this; } @@ -133,6 +136,12 @@ public int contentLength() { return _content.length(); } + + 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; From 6d35a5723922aa9d6355950204cebfa445999afb Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Wed, 13 Feb 2013 22:07:46 +0100 Subject: [PATCH 05/15] BUGFIX: The 'onKeyUp' event is not fired if you pressng a tab key quickly after an input and it seems that the 'onChange' event does not work reliable in combination with 'onKeyUp'. Therefore, use 'onBlur'! --- Frameworks/Ajax/Ajax/WebServerResources/wonder.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js index 9c24de1cd85..24179b74425 100644 --- a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js +++ b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js @@ -1001,7 +1001,10 @@ Form.Element.ExtendedEventObserver = Class.create(Form.Element.EventObserver, { case 'text': Event.observe(element, 'change', this.onElementEvent.bind(this)); if (this.actOnKeyUp) + { Event.observe(element, 'keyup', this.onElementEvent.bind(this)); + Event.observe(element, 'blur', this.onElementEvent.bind(this)); + } break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); From 841b58c95a8bf3567fbdf0e9871eac460f8abfa9 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Thu, 28 Feb 2013 21:29:50 +0100 Subject: [PATCH 06/15] Fix by adding fallback appendToReponse wrapper --- Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java index ef3c3b172a3..c37366f9174 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java @@ -131,6 +131,9 @@ public void appendToResponse(WOResponse response, WOContext context) { AjaxUtils.appendScriptFooter(response); } + 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 actOnKeyUp) { WOComponent component = context.component(); String submitButtonName = nameInContext(context, component, element); From 4b8664f695bbe7e4a3b0163a9578987d3a626697 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Tue, 16 Apr 2019 15:44:24 +0200 Subject: [PATCH 07/15] Rework, removing reference to no longer existing method --- .../Ajax/Sources/er/ajax/AjaxUpdateContainer.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java index 1d8412532d1..b3ab24f0d74 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java @@ -323,22 +323,24 @@ public static String updateContainerID(WORequest request) { public static void setUpdateContainerID(WORequest request, String updateContainerID) { if (updateContainerID != null) { + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); + if(updateContainerID.indexOf(",") >= 0) - AjaxUtils.mutableUserInfo(request).setObjectForKey(Arrays.asList(updateContainerID.split(",")), CONTAINER_LIST_KEY); + userInfo.setObjectForKey(Arrays.asList(updateContainerID.split(",")), CONTAINER_LIST_KEY); else - AjaxUtils.mutableUserInfo(request).setObjectForKey(updateContainerID, ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID); + userInfo.setObjectForKey(updateContainerID, ERXAjaxApplication.KEY_UPDATE_CONTAINER_ID); } } public static List updateContainerIDList(WORequest request) { - NSDictionary userInfo = AjaxUtils.mutableUserInfo(request); + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); return (List) userInfo.objectForKey(CONTAINER_LIST_KEY); } public static void setRangeForContainerID(WORequest request, String updateContainerID, NSRange range) { - NSMutableDictionary userInfo = AjaxUtils.mutableUserInfo(request); + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); NSMutableDictionary rangeDict = (NSMutableDictionary) userInfo.objectForKey(CONTAINER_RANGEDICT_KEY); if(rangeDict == null) { @@ -351,7 +353,7 @@ public static void setRangeForContainerID(WORequest request, String updateContai public static NSRange rangeForContainerID(WORequest request, String updateContainerID) { - NSMutableDictionary userInfo = AjaxUtils.mutableUserInfo(request); + NSMutableDictionary userInfo = ERXWOContext.contextDictionary(); NSMutableDictionary rangeDict = (NSMutableDictionary) userInfo.objectForKey(CONTAINER_RANGEDICT_KEY); if(rangeDict != null) return rangeDict.objectForKey(updateContainerID); From f6b778fb9d27cdaf879498714e9cb5333dbfbba7 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Tue, 16 Apr 2019 15:45:50 +0200 Subject: [PATCH 08/15] handle context id replacement in case of multiple update container ids --- Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java index 08909c3ebe7..1cc77c7bc46 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java @@ -3,6 +3,7 @@ import java.util.Enumeration; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,10 +15,12 @@ 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 @@ -95,6 +98,8 @@ public WOResponse generateResponse() { if(updateContainerIDList != null) { + _context.request().setHeader(StringUtils.join(updateContainerIDList, ','), ERXAjaxSession.PAGE_REPLACEMENT_CACHE_LOOKUP_KEY); + WOApplication.application().appendToResponse(this, _context); StringBuilder c2 = new StringBuilder(); From b600077b1e1b3566081d2b4e068a1be395d3c672 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Tue, 16 Apr 2019 15:50:12 +0200 Subject: [PATCH 09/15] Modifications to support multiple update containers in one request-response-loop when using 'AjaxUpdateLink' elements. --- .../Ajax/Ajax/WebServerResources/wonder.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js index 24179b74425..9cc147766c9 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) { From 69e8c3dad06bbead2e6c0e673f4c20be15d00c3c Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Tue, 16 Apr 2019 15:53:15 +0200 Subject: [PATCH 10/15] AjaxObserveField add API doc --- Frameworks/Ajax/Ajax/Components/AjaxObserveField.api | 1 + 1 file changed, 1 insertion(+) diff --git a/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api b/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api index 11ac5ffa889..4250592b320 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 From 24a9fce49c738ca1ffe9f1af080890f046d0bdd6 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Tue, 16 Apr 2019 15:55:10 +0200 Subject: [PATCH 11/15] AjaxObserveField: input type="number" -> treat like text --- Frameworks/Ajax/Ajax/WebServerResources/wonder.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js index 9cc147766c9..c3d54e0b72b 100644 --- a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js +++ b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js @@ -1013,6 +1013,7 @@ Form.Element.ExtendedEventObserver = Class.create(Form.Element.EventObserver, { Event.observe(element, 'click', this.onElementEvent.bind(this)); break; case 'text': + case 'number': Event.observe(element, 'change', this.onElementEvent.bind(this)); if (this.actOnKeyUp) { From 92c9f0b4fd9c3f1ffb86aa73f7abf0db1016f844 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Tue, 16 Apr 2019 15:59:53 +0200 Subject: [PATCH 12/15] make AjaxUpdateTrigger symmetrical, accepting a comma separated string, too --- .../Sources/er/ajax/AjaxUpdateTrigger.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) 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"); + } + } From 4dcb9cee2a142d4c6316f98c820013cde5bec7e5 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Tue, 16 Apr 2019 16:19:40 +0200 Subject: [PATCH 13/15] rework AjaxObserveField to listen on input rather than on keyUp --- .../Ajax/Ajax/Components/AjaxObserveField.api | 2 +- .../Ajax/Sources/er/ajax/AjaxObserveField.java | 10 +++++----- .../Sources/er/ajax/AjaxUpdateContainer.java | 4 ++-- .../Ajax/Ajax/WebServerResources/wonder.js | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api b/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api index 4250592b320..d8c99f009fb 100644 --- a/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api +++ b/Frameworks/Ajax/Ajax/Components/AjaxObserveField.api @@ -18,6 +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 c37366f9174..43889241e7f 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxObserveField.java @@ -63,7 +63,7 @@ * @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. - * @binding actOnKeyUp When true, keyup events in text input fields lead to an immediate action + * @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) { @@ -103,7 +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 actOnKeyUp = booleanValueForBinding("actOnKeyUp", false, component); + boolean actOnInput = booleanValueForBinding("actOnInput", false, component); boolean observeFieldDescendents; if (observeFieldID != null) { observeFieldDescendents = false; @@ -127,14 +127,14 @@ public void appendToResponse(WOResponse response, WOContext context) { response.appendContentString(""); } AjaxUtils.appendScriptHeader(response); - AjaxObserveField.appendToResponse(response, context, this, observeFieldID, observeFieldDescendents, updateContainerID, fullSubmit, options, actOnKeyUp); + 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) { 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 actOnKeyUp) { + 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<>(); @@ -163,7 +163,7 @@ public static void appendToResponse(WOResponse response, WOContext context, Ajax response.appendContentString(observeDelay); response.appendContentString(", "); AjaxOptions.appendToResponse(observerOptions, response, context); - response.appendContentString(", " + actOnKeyUp +");"); + response.appendContentString(", " + actOnInput +");"); } public static String nameInContext(WOContext context, WOComponent component, AjaxDynamicElement element) { diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java index b3ab24f0d74..4f49876b805 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxUpdateContainer.java @@ -256,8 +256,8 @@ public void appendToResponse(WOResponse response, WOContext context) { if (observeFieldID != null) { boolean fullSubmit = booleanValueForBinding("fullSubmit", false, component); - boolean actOnKeyUp = booleanValueForBinding("actOnKeyUp", false, component); - AjaxObserveField.appendToResponse(response, context, this, observeFieldID, false, id, fullSubmit, createObserveFieldOptions(component), actOnKeyUp); + boolean actOnInput = booleanValueForBinding("actOnInput", false, component); + AjaxObserveField.appendToResponse(response, context, this, observeFieldID, false, id, fullSubmit, createObserveFieldOptions(component), actOnInput); } response.appendContentString("AUC.register('" + id + "'"); diff --git a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js index c3d54e0b72b..ded85cba8db 100644 --- a/Frameworks/Ajax/Ajax/WebServerResources/wonder.js +++ b/Frameworks/Ajax/Ajax/WebServerResources/wonder.js @@ -484,15 +484,15 @@ var AjaxSubmitButton = { new Ajax.Request(finalUrl, finalOptions); }, - observeDescendentFields: function(updateContainerID, containerID, observeFieldFrequency, partial, observeDelay, options, actOnKeyUp) { + 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, actOnKeyUp); + AjaxSubmitButton.observeField(updateContainerID, element, observeFieldFrequency, partial, observeDelay, options, actOnInput); } }); }, - observeField: function(updateContainerID, formFieldID, observeFieldFrequency, partial, observeDelay, options, actOnKeyUp) { + 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 @@ -531,7 +531,7 @@ var AjaxSubmitButton = { new Form.Element.RadioButtonObserver($(formFieldID), submitFunction); } else { - new Form.Element.ExtendedEventObserver($(formFieldID), submitFunction, actOnKeyUp); + new Form.Element.ExtendedEventObserver($(formFieldID), submitFunction, actOnInput); } } else { @@ -1000,8 +1000,8 @@ Form.Element.RadioButtonObserver = Class.create(Form.Element.EventObserver, { }); Form.Element.ExtendedEventObserver = Class.create(Form.Element.EventObserver, { - initialize: function($super, element, callback, actOnKeyUp) { - this.actOnKeyUp = actOnKeyUp; + initialize: function($super, element, callback, actOnInput) { + this.actOnInput = actOnInput; $super(element, callback); }, @@ -1015,9 +1015,9 @@ Form.Element.ExtendedEventObserver = Class.create(Form.Element.EventObserver, { case 'text': case 'number': Event.observe(element, 'change', this.onElementEvent.bind(this)); - if (this.actOnKeyUp) + if (this.actOnInput) { - Event.observe(element, 'keyup', this.onElementEvent.bind(this)); + Event.observe(element, 'input', this.onElementEvent.bind(this)); Event.observe(element, 'blur', this.onElementEvent.bind(this)); } break; From 1dc850af30a276786a3ec746638a61fc89f38975 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Thu, 18 Apr 2019 13:18:35 +0200 Subject: [PATCH 14/15] Add comment --- Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java index 1cc77c7bc46..4ef2446c8a2 100644 --- a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java +++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxResponse.java @@ -142,6 +142,8 @@ 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))) From ba2e1f7fd131fa330af4230932f7905f594d5b14 Mon Sep 17 00:00:00 2001 From: Markus Stoll Date: Sun, 16 Jun 2019 20:22:59 +0200 Subject: [PATCH 15/15] Add demo for multiple container updates --- .../AjaxExampleComponent.html | 1 + .../AjaxExampleComponent.wod | 4 ++ .../MultiUpdateExample.html | 32 +++++++++++ .../MultiUpdateExample.wod | 47 ++++++++++++++++ .../MultiUpdateExample.woo | 4 ++ .../UpdateTriggerExample.html | 6 +- .../Sources/MultiUpdateExample.java | 56 +++++++++++++++++++ .../Sources/UpdateTriggerExample.java | 13 ++++- 8 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.html create mode 100644 Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.wod create mode 100644 Examples/Ajax/AjaxExample/Components/MultiUpdateExample.wo/MultiUpdateExample.woo create mode 100644 Examples/Ajax/AjaxExample/Sources/MultiUpdateExample.java 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() {