package er.ajax.mootools; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import com.webobjects.appserver.WOActionResults; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOElement; import com.webobjects.appserver.WORequest; import com.webobjects.appserver.WOResponse; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import er.ajax.AjaxComponent; import er.ajax.AjaxOption; import er.ajax.AjaxOptions; import er.ajax.AjaxUtils; import er.extensions.appserver.ERXWOContext; import er.extensions.foundation.ERXStringUtilities; import er.extensions.foundation.ERXValueUtilities; public class MTAjaxAutoComplete extends AjaxComponent { private static final long serialVersionUID = 1L; public String divName; public String fieldName; public String indicatorName; public MTAjaxAutoComplete(WOContext context) { super(context); } @Override protected void addRequiredWebResources(WOResponse res) { MTAjaxUtils.addScriptResourceInHead(context(), res, "MooTools", MTAjaxUtils.MOOTOOLS_CORE_JS); MTAjaxUtils.addScriptResourceInHead(context(), res, "MooTools", "scripts/plugins/autocomplete/AutoCompleter.js"); } /** * Overridden to set the IDs for the field and the div tag. */ @Override public void awake() { super.awake(); divName = safeElementID() + "_div"; fieldName = safeElementID() + "_field"; indicatorName = safeElementID() + "_indicator"; } @Override public void sleep() { divName = null; fieldName = null; indicatorName = null; super.sleep(); } /** * Overridden because the component is stateless */ @Override public boolean isStateless() { return true; } public String indicator() { String indicator = (String)valueForBinding("indicator"); if (indicator == null && valueForBinding("indicatorFilename") != null) { indicator = "'" + indicatorName + "'"; } return indicator; } protected NSDictionary<String, String> createAjaxOptions() { NSMutableArray<AjaxOption> ajaxOptionsArray = new NSMutableArray<AjaxOption>(); ajaxOptionsArray.addObject(new AjaxOption("tokens", AjaxOption.STRING_ARRAY)); ajaxOptionsArray.addObject(new AjaxOption("frequency", AjaxOption.NUMBER)); ajaxOptionsArray.addObject(new AjaxOption("minChars", AjaxOption.NUMBER)); ajaxOptionsArray.addObject(new AjaxOption("indicator", indicator(), AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("updateElement", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("afterUpdateElement", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("onShow", AjaxOption.SCRIPT)); ajaxOptionsArray.addObject(new AjaxOption("fullSearch", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("partialSearch", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("defaultValue", AjaxOption.STRING)); ajaxOptionsArray.addObject(new AjaxOption("select", AjaxOption.STRING)); ajaxOptionsArray.addObject(new AjaxOption("autoSelect", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("choices", AjaxOption.NUMBER)); ajaxOptionsArray.addObject(new AjaxOption("partialChars", AjaxOption.NUMBER)); ajaxOptionsArray.addObject(new AjaxOption("ignoreCase", AjaxOption.BOOLEAN)); ajaxOptionsArray.addObject(new AjaxOption("activateOnFocus", AjaxOption.BOOLEAN)); NSMutableDictionary<String, String> options = AjaxOption.createAjaxOptionsDictionary(ajaxOptionsArray, this); return options; } /** * Overridden to add the initialization javascript for the auto completer. */ @Override public void appendToResponse(WOResponse res, WOContext ctx) { super.appendToResponse(res, ctx); boolean isDisabled = hasBinding("disabled") && ((Boolean) valueForBinding("disabled")).booleanValue(); if ( !isDisabled ) { boolean isLocal = hasBinding("isLocal") && ((Boolean) valueForBinding("isLocal")).booleanValue(); if (isLocal) { StringBuilder str = new StringBuilder(); boolean isLocalSharedList = hasBinding("isLocalSharedList") && ((Boolean) valueForBinding("isLocalSharedList")).booleanValue(); String listJS = null; if (isLocalSharedList) { String varName = (String) valueForBinding("localSharedVarName"); @SuppressWarnings("unchecked") NSMutableDictionary<String, String> userInfo = ERXWOContext.contextDictionary(); if (userInfo.objectForKey(varName) == null) { String ljs = listeJS(); AjaxUtils.addScriptCodeInHead(res, ctx, "var " + varName + " = " + ljs + ";"); userInfo.setObjectForKey(ljs, varName); } listJS = varName; } else { listJS = listeJS(); } str.append("<script type=\"text/javascript\">\n// <![CDATA[\n"); str.append("new MTAutocompleter.Local('"); str.append(fieldName); str.append("','"); str.append(divName); str.append("',"); str.append(listJS); str.append(','); AjaxOptions.appendToBuffer(createAjaxOptions(), str, ctx); str.append(");\n// ]]>\n</script>\n"); res.appendContentString(String.valueOf(str)); } else { String actionUrl = AjaxUtils.ajaxComponentActionUrl(ctx); AjaxUtils.appendScriptHeader(res); res.appendContentString("new Request.Autocompleter('"+fieldName+"', '"+divName+"', '"+actionUrl+"', "); AjaxOptions.appendToResponse(createAjaxOptions(), res, ctx); res.appendContentString(");"); AjaxUtils.appendScriptFooter(res); } } } String listeJS() { StringBuilder str = new StringBuilder(); str.append("new Array("); @SuppressWarnings("unchecked") NSArray<Object> list = (NSArray<Object>) valueForBinding("list"); int max = list.count(); String cnt = ""; boolean hasItem = hasBinding("item"); for (int i = 0; i < max; i++) { Object ds = list.objectAtIndex(i); if (i > 0) { str.append(','); } str.append("\n\""); if (hasItem) { setValueForBinding(ds, "item"); } Object displayValue = valueForBinding("displayString", valueForBinding("item", ds)); str.append(displayValue.toString()); // TODO: We should escape the javascript string delimiter (") to keep the javascript interpreter happy. //str.append(displayValue.toString().replaceAll("\"", "\\\\\\\\\"")); // doesn't work str.append(cnt); str.append("\""); } str.append(')'); return String.valueOf(str); } public String stringValue() { String strValue = null; if (hasBinding("selection")) { Object selection = valueForBinding("selection"); if (selection != null) { if (hasBinding("displayString")) { setValueForBinding(selection, "item"); strValue = displayStringForValue(valueForBinding("value")); } else { strValue = String.valueOf(selection); } } else strValue = (String) valueForBinding("value"); } else if (hasBinding("value")) { strValue = (String) valueForBinding("value"); } return strValue; } protected String displayStringForValue(Object value) { Object displayValue = valueForBinding("displayString", valueForBinding("item", value)); String displayString = displayValue == null ? null : displayValue.toString(); return displayString; } protected int maxItems() { int maxItems = ERXValueUtilities.intValueWithDefault(valueForBinding("maxItems"), 50); return maxItems; } public void setStringValue(String strValue) { if (hasBinding("selection")) { Object selection = null; if (strValue != null) { @SuppressWarnings("unchecked") NSArray<Object> values = (NSArray<Object>) valueForBinding("list"); int maxItems = maxItems(); int itemsCount = 0; for(Enumeration<Object> e = values.objectEnumerator(); e.hasMoreElements() && itemsCount++ < maxItems;) { Object value = e.nextElement(); setValueForBinding(value, "item"); String displayString = displayStringForValue(value); if (ERXStringUtilities.stringEqualsString(displayString, strValue)) { selection = value; break; } } } setValueForBinding(selection, "selection"); } setValueForBinding(strValue, "value"); } protected void appendItemToResponse(Object value, WOElement child, boolean hasItem, WOResponse response, WOContext context) { response.appendContentString("<li>"); if(hasItem && child != null) { setValueForBinding(value, "item"); context._setCurrentComponent(parent()); child.appendToResponse(response, context); context._setCurrentComponent(this); } else { if(hasItem) { setValueForBinding(value, "item"); } response.appendContentString(displayStringForValue(value)); } response.appendContentString("</li>"); } /** * Handles the Ajax request. Checks for the form value in the edit field, * pushes it up to the parent and pulls the "list" binding. The parent is * responsible for returning a list with some items that match the current value. */ @Override public WOActionResults handleRequest(WORequest request, WOContext context) { // String inputString = request.contentString(); String fieldValue = context.request().stringFormValueForKey(fieldName); setValueForBinding(fieldValue, "value"); WOResponse response = AjaxUtils.createResponse(request, context); response.appendContentString("<ul>"); int maxItems = maxItems(); int itemsCount = 0; Object values = valueForBinding("list"); WOElement child = _childTemplate(); boolean hasItem = hasBinding("item"); if (values instanceof NSArray) { for(@SuppressWarnings("unchecked") Enumeration<Object> valueEnum = ((NSArray<Object>)values).objectEnumerator(); valueEnum.hasMoreElements() && itemsCount++ < maxItems;) { appendItemToResponse(valueEnum.nextElement(), child, hasItem, response, context); } } else if (values instanceof List) { for(@SuppressWarnings("unchecked") Iterator<Object> iter = ((List<Object>)values).iterator(); iter.hasNext() && itemsCount++ < maxItems;) { appendItemToResponse(iter.next(), child, hasItem, response, context); } } response.appendContentString("</ul>"); return response; } public String zcontainerName() { return "ZContainer" + divName; } }