/* * � Copyright IBM Corp. 2014, 2015 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * Author: Maire Kehoe (mkehoe@ie.ibm.com) * Date: 17 Feb 2014 * MobileTypeAheadInputRenderer.java */ package com.ibm.xsp.extlib.renderkit.html_extended.mobile; import java.io.IOException; import java.util.List; import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.convert.Converter; import com.ibm.commons.util.StringUtil; import com.ibm.xsp.FacesExceptionEx; import com.ibm.xsp.complex.Attr; import com.ibm.xsp.component.FacesAttrsObject; import com.ibm.xsp.component.UIInputEx; import com.ibm.xsp.component.UIViewRootEx; import com.ibm.xsp.component.xp.XspTypeAhead; import com.ibm.xsp.converter.ListConverter; import com.ibm.xsp.renderkit.html_basic.TypeAheadInputRenderer; import com.ibm.xsp.renderkit.html_basic.TypeAheadRenderer; import com.ibm.xsp.resource.DojoModuleResource; import com.ibm.xsp.util.JSUtil; import com.ibm.xsp.util.TypedUtil; /** * * @author Maire Kehoe (mkehoe@ie.ibm.com) */ public class MobileTypeAheadInputRenderer extends TypeAheadInputRenderer { private static final DojoModuleResource COMBOBOX_MODULE = new DojoModuleResource("extlib.dijit.mobile.TypeAheadCombo");//$NON-NLS-1$ /* (non-Javadoc) * @see com.ibm.xsp.renderkit.html_basic.BasicInputTextRenderer#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent) */ @Override public void encodeBegin(FacesContext context, UIComponent component) throws IOException { Map<String, Object> attrs = TypedUtil.getAttributes(component); String initializedKey = "_initialized_MobileTypeAheadInputRenderer"; //$NON-NLS-1$ if( null == attrs.get(initializedKey) ){ UIInput input = (UIInput) component; XspTypeAhead typeAhead = findTypeAheadChild(input); if( null != typeAhead && typeAhead.isTypeAheadEnabled(context)){ initTypeAhead(context, input, typeAhead); validateNoSeparator(context, input, typeAhead); } attrs.put(initializedKey, "true"); //$NON-NLS-1$ //$NON-NLS-2$ } super.encodeBegin(context, component); } private void validateNoSeparator(FacesContext context, UIInput input, XspTypeAhead typeAhead) { boolean separatorDetected = false; String separatorType = null; if( null != typeAhead.getTokens() ){ separatorDetected = true; separatorType = "xp:typeAhead tokens"; //$NON-NLS-1$ } if( ! separatorDetected ){ if( input instanceof UIInputEx){ UIInputEx inputEx = (UIInputEx) input; String separator = inputEx.getMultipleSeparator(); if( null != separator ){ separatorDetected = true; separatorType = "xp:inputText multipleSeparator"; //$NON-NLS-1$ } } } if( ! separatorDetected ){ Converter converter = input.getConverter(); if( converter instanceof ListConverter ){ separatorDetected = true; separatorType = "xp:convertList"; //$NON-NLS-1$ } } if( separatorDetected ){ // String left untagged - hoping to support multiple before begin translating strings for next product release String msg = "The mobile xp:typeAhead control does not support entering multiple values in the edit box. A multiple value separator has been detected: {0}"; // $NLS-MobileTypeAheadInputRenderer.ThemobilexptypeAheadcontroldoesno-1$ msg = StringUtil.format(msg, separatorType); throw new FacesExceptionEx(msg); } } private void initTypeAhead(FacesContext context, UIInput input, XspTypeAhead typeAhead) { // prevent setParent after restoreState from overwriting the rendererType set in the theme file String themeConfiguredInputRendererType = input.getRendererType(); typeAhead.setParentRendererType(themeConfiguredInputRendererType); // TODO have to do this overriding here instead of in the theme file // because XspTypeAhead doesn't implement ThemeControl String existingDojoType = typeAhead.getDojoType(); if( null == existingDojoType ){ String dojoType= COMBOBOX_MODULE.getName(); typeAhead.setDojoType(dojoType); } String existingStoreType = typeAhead.getDojoStoreType(); if( null == existingStoreType ){ String storeType = "extlib.store.TypeAheadStore"; //$NON-NLS-1$ typeAhead.setDojoStoreType(storeType); } } /* (non-Javadoc) * @see com.ibm.xsp.renderkit.html_basic.TypeAheadInputRenderer#writeTypeAheadAttributes(javax.faces.context.FacesContext, javax.faces.component.UIComponent, javax.faces.context.ResponseWriter, java.lang.String) */ @Override protected void writeTypeAheadAttributes(FacesContext context, UIComponent inputComponent, ResponseWriter writer, String currentValue) throws IOException { super.writeTypeAheadAttributes(context, inputComponent, writer, currentValue); XspTypeAhead child = findTypeAheadChild(inputComponent); if (null == child || ! child.isTypeAheadEnabled(context) ){ // if this input should be rendered as a typeAhead return; } // data-dojo-props="list:'view__id1_appPage1_content_typeAhead1', searchAttr:'value', labelAttr:'label', labelType:'html'" String dataJsId = TypeAheadRenderer.toJavaScriptId(child.getClientId(context)); StringBuilder b = new StringBuilder(); b.append("list:"); //$NON-NLS-1$ b.append('\''); JSUtil.appendJavaScriptString(b, dataJsId); b.append('\''); b.append(", searchAttr:'value', labelAttr:'label', labelType:'html'"); //$NON-NLS-1$ writer.writeAttribute("data-dojo-props", b.toString(), null); //$NON-NLS-1$ // <head> ... <script>dojo.require('dojox.mobile.ComboBox')</script> ...</head> String dojoType = child.getDojoType(); if(StringUtil.equals(dojoType, COMBOBOX_MODULE.getName())) { UIViewRootEx rootEx = (UIViewRootEx) context.getViewRoot(); // <script> dojo.require('dojox.mobile.ComboBox') </script> rootEx.addEncodeResource(context,COMBOBOX_MODULE); } boolean foundSpellCheckAttr = false; if( inputComponent instanceof FacesAttrsObject ){ List<Attr> attrsList = ((FacesAttrsObject)inputComponent).getAttrs(); if( null != attrsList ){ for (Attr attr : attrsList) { if( "spellcheck".equals(attr.getName()) ){ //$NON-NLS-1$ foundSpellCheckAttr = true; break; } } } } if( ! foundSpellCheckAttr ){ // spellcheck="false" // http://www.w3.org/TR/html5/editing.html#spelling-and-grammar-checking // Note, by agreement between Maire & Tony, we expect the users to type in partial // values into the edit box, and then to choose from the suggestions in the dropdown, // but the partial values are likely to trigger the mobile device's spell checker // to prompt with other suggested spellings (since the partial values are not full words), // so disabling the spell checker by default to avoid the annoying prompt. writer.writeAttribute("spellcheck", "false", null); //$NON-NLS-1$ //$NON-NLS-2$ } } private XspTypeAhead findTypeAheadChild(UIComponent component) { if(component.getChildCount()>0) { for (UIComponent child : TypedUtil.getChildren(component)) { if( child instanceof XspTypeAhead ){ return (XspTypeAhead) child; } } } return null; } }