/* * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.lwuit.html; import com.sun.lwuit.Button; import com.sun.lwuit.ButtonGroup; import com.sun.lwuit.CheckBox; import com.sun.lwuit.ComboBox; import com.sun.lwuit.Command; import com.sun.lwuit.RadioButton; import com.sun.lwuit.TextArea; import com.sun.lwuit.events.ActionEvent; import com.sun.lwuit.plaf.UIManager; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; /** * HTMLForm is an object that holds all the data related to a form within HTML. * Note that it is not related in any way to LWUIT's Form. * * @author Ofir Leitner */ class HTMLForm { /** * The default text on forms' submit button */ private static final String DEFAULT_SUBMIT_TEXT = "Submit"; /** * The default text on forms' reset button */ private static final String DEFAULT_RESET_TEXT = "Clear"; Hashtable comps = new Hashtable(); Hashtable defaultValues = new Hashtable(); Vector defaultCheckedButtons = new Vector(); Vector defaultUncheckedButtons = new Vector(); Hashtable inputFormats = new Hashtable(); Hashtable buttonGroups = new Hashtable(); Vector emptyOk = new Vector(); Vector emptyNotOk = new Vector(); HTMLComponent htmlC; String action; String encType; boolean isPostMethod; boolean hasSubmitButton; NamedCommand submitCommand; NamedCommand resetCommand; /** * Constructs the HTMLForm * * @param htmlC The HTMLComponent containing this form * @param action The action of this form (the URL) * @param method The method of this form (get/post) */ HTMLForm(HTMLComponent htmlC,String action,String method,String encType) { this.htmlC=htmlC; this.action=htmlC.convertURL(action); this.encType=encType; if (htmlC.getHTMLCallback()!=null) { int linkProps=htmlC.getHTMLCallback().getLinkProperties(htmlC, this.action); if ((linkProps & HTMLCallback.LINK_FORBIDDEN)!=0) { this.action=null; } } this.isPostMethod=((method!=null) && (method.equalsIgnoreCase("post"))); submitCommand=new NamedCommand(getDefaultSubmitText(), this, true); resetCommand=new NamedCommand(getDefaultResetText(), this, false); } /** * Returns the default submit command text * * @return the default submit command text */ static String getDefaultSubmitText() { return UIManager.getInstance().localize("html.submit", DEFAULT_SUBMIT_TEXT); } /** * Returns the default reset command text * * @return the default reset command text */ static String getDefaultResetText() { return UIManager.getInstance().localize("html.reset", DEFAULT_RESET_TEXT); } /** * Returns the submit command of this form * * @return the submit command of this form */ Command getSubmitCommand() { return submitCommand; } /** * Returns the reset command of this form * * @return the reset command of this form */ Command getResetCommand() { return resetCommand; } /** * Returns the text used for the submit button on this form * * @return the text used for the submit button on this form */ void setSubmitText(String text) { submitCommand.setCommandName(text); } /** * Returns the text used for the reset button on this form * * @return the text used for the reset button on this form */ void setResetText(String text) { resetCommand.setCommandName(text); } /** * Adds an input field to the form, note that unlike adding to a LWUIT form here the components are added logically only to query them for their value on submit. * * @param key The input field's name/id * @param input The field itself (either the component or the value for a hidden field) * @param defaultValue The default value of the field (or null if none) */ void addInput(String key,Object input,String defaultValue) { if (defaultValue!=null) { // Default values are added even before the key is checked, since even if the input component is unnamed, the form still needs to be able to reset it defaultValues.put(input,defaultValue); } if (key==null) { return; //no id } comps.put(key,input); if ((htmlC.getHTMLCallback()!=null) && (input instanceof TextArea)) { String autoVal=htmlC.getHTMLCallback().getAutoComplete(htmlC, action, key); if (autoVal!=null) { ((TextArea)input).setText(autoVal); } } } /** * * @param ta The textarea/field * @param inputFormat An input format verifier (or null if none) */ void setInputFormat(TextArea ta,HTMLInputFormat inputFormat) { inputFormats.put(ta, inputFormat); } /** * Sets whether the specified input field can be left empty or not * * @param ta The TextArea * @param ok true if can be left empty, false otherwise */ void setEmptyOK(TextArea ta,boolean ok) { if (ok) { emptyOk.addElement(ta); } else { emptyNotOk.addElement(ta); } } /** * Sets the default value for the specified input field * * @param input The input field to set the default value to * @param defaultValue The default value */ void setDefaultValue(Object input,Object defaultValue) { if ((input!=null) &&(defaultValue!=null)) { defaultValues.put(input,defaultValue); } } /** * Adds the specified CheckBox to the form. * Note that unlike adding to a LWUIT form here the components are added logically only to query them for their value on submit. * * @param key The CheckBox's name/id * @param cb The CheckBox * @param value The value of the checkbox */ void addCheckBox(String key,CheckBox cb,String value) { if (cb.isSelected()) { defaultCheckedButtons.addElement(cb); } else { defaultUncheckedButtons.addElement(cb); } if (key==null) { return; //no id } Hashtable internal=(Hashtable)comps.get(key); if (internal==null) { internal=new Hashtable(); comps.put(key,internal); } internal.put(cb, value); } /** * Adds the specified RadioButton to the form. * Note that unlike adding to a LWUIT form here the components are added logically only to query them for their value on submit. * * @param key The CheckBox's name/id * @param rb The RadioButton to add * @param value The value of the checkbox */ void addRadioButton(String key,RadioButton rb,String value) { if (rb.isSelected()) { defaultCheckedButtons.addElement(rb); } else { defaultUncheckedButtons.addElement(rb); } if (key==null) { return; //no id } Hashtable internal=(Hashtable)comps.get(key); ButtonGroup group=null; if (internal==null) { internal=new Hashtable(); comps.put(key,internal); group=new ButtonGroup(); buttonGroups.put(key, group); } else { group=(ButtonGroup)buttonGroups.get(key); } group.add(rb); internal.put(rb, value); } /** * Returns the number of fields in the form * * @return the number of fields in the form */ int getNumFields() { return comps.size(); } /** * Called when the a form submit is needed. * This querys all form fields, creates a URL accordingly and sets it to the HTMLComponent */ void submit() { if (action==null) { return; } boolean error=false; //If this is turned to true anywhere, the form will not be submitted String url=action; String params=null; if (comps.size()>0) { params=""; for(Enumeration e=comps.keys();e.hasMoreElements();) { String key=(String)e.nextElement(); Object input=comps.get(key); key=encodeString(key); String value=""; if (input instanceof String) { //hidden value=encodeString((String)input); params+=key+"="+value+"&"; } else if (input instanceof Hashtable) { //checkbox / radiobutton Hashtable options=(Hashtable)input; for(Enumeration e2=options.keys();e2.hasMoreElements();) { Button b = (Button)e2.nextElement(); if (b.isSelected()) { params+=key+"="+encodeString((String)options.get(b))+"&"; } } } else if (input instanceof TextArea) { //catches both textareas and text input fields TextArea tf=((TextArea)input); String text=tf.getText(); String errorMsg=null; if (HTMLComponent.SUPPORT_INPUT_FORMAT) { boolean ok=false; if (text.equals("")) { // check empty - Note that emptyok/-wap-input-required overrides input format if (emptyNotOk.contains(tf)) { errorMsg=UIManager.getInstance().localize("html.format.emptynotok", "Field can't be empty"); error=true; } else if (emptyOk.contains(tf)) { ok=true; } } if ((!error) && (!ok)) { // If there's already an error or it has been cleared by the emptyOK field, no need to check HTMLInputFormat inputFormat=(HTMLInputFormat)inputFormats.get(tf); if ((inputFormat!=null) && (!inputFormat.verifyString(text))) { String emptyStr=""; if (emptyOk.contains(tf)) { emptyStr=UIManager.getInstance().localize("html.format.oremptyok", " or an empty string"); } else if (emptyNotOk.contains(tf)) { emptyStr=UIManager.getInstance().localize("html.format.andemptynotok", " and cannot be an empty string"); } errorMsg=UIManager.getInstance().localize("html.format.errordesc", "Malformed text. Correct value: ")+inputFormat.toString()+emptyStr; error=true; } } } if (htmlC.getHTMLCallback()!=null) { int type=HTMLCallback.FIELD_TEXT; if ((tf.getConstraint() & TextArea.PASSWORD)!=0) { type=HTMLCallback.FIELD_PASSWORD; } text=htmlC.getHTMLCallback().fieldSubmitted(htmlC, tf,url, key, text, type,errorMsg); } if (errorMsg==null) { params+=key+"="+encodeString(text)+"&"; } } else if (input instanceof ComboBox) { // drop down lists (single selection) Object item=((ComboBox)input).getSelectedItem(); if (item instanceof OptionItem) { value=((OptionItem)item).getValue(); params+=key+"="+encodeString(value)+"&"; } // if not - value may be an OPTGROUP label in an only optgroup combobox } else if (input instanceof MultiComboBox) { // drop down lists (multiple selection) Vector selected=((MultiComboBox)input).getSelected(); for(int i=0;i<selected.size();i++) { Object item=selected.elementAt(i); if (item instanceof OptionItem) { value=((OptionItem)item).getValue(); params+=key+"="+encodeString(value)+"&"; } // if not - value may be an OPTGROUP label in an only optgroup combobox } } } if (params.endsWith("&")) { //trim the extra & params=params.substring(0, params.length()-1); } } if (!error) { DocumentInfo docInfo=new DocumentInfo(url, params, isPostMethod); if ((encType!=null) && (!encType.equals(""))) { docInfo.setEncoding(encType); } htmlC.setPage(docInfo); } } /** * Encodes the specified string to "percent-encoding" or URL encoding. * This encodes reserved, unsafe and unicode characters * * @param str The string to be encoded * @return A percent-encoding of the string (safe characters remain the same) */ private String encodeString(String str) { if (str==null) { return ""; } String encodedStr=""; for(int i=0;i<str.length();i++) { char c=str.charAt(i); if ( // Checks for unreserved characters that RFC 3986 defines that shouldn't be encoded ((c>='a') && (c<='z')) || ((c>='A') && (c<='Z')) || ((c>='0') && (c<='9')) || (c=='-') || (c=='.') || (c=='_') || (c=='~')) { encodedStr+=c; } else if ((c>0x80) && (c<0xffff)) { // UTF encoding - See http://en.wikipedia.org/wiki/UTF-8 int firstLiteral = c/256; int secLiteral = c%256; if (c<0x07ff) { // 2 literals unicode firstLiteral=192+(firstLiteral<<2)+(secLiteral>>6); secLiteral=128+secLiteral%192; encodedStr+="%"+Integer.toHexString(firstLiteral).toUpperCase()+"%"+Integer.toHexString(secLiteral).toUpperCase(); } else { // 3 literals unicode firstLiteral=224+(firstLiteral>>4); secLiteral=128+((firstLiteral%16)<<2)+(secLiteral>>6); int thirdLiteral=128+secLiteral%192; encodedStr+="%"+Integer.toHexString(firstLiteral).toUpperCase()+"%"+Integer.toHexString(secLiteral).toUpperCase() +"%"+Integer.toHexString(thirdLiteral).toUpperCase(); } // TODO - ? - support for 4 literals code? porbably not even included in UTF8 } else { String prefix="%"; if (c<16) { prefix+="0"; //For a value lesser than 16, we'd like to get %0F and not %F } encodedStr+=prefix+Integer.toHexString((int)c).toUpperCase(); } } return encodedStr; } /** * Called when a form reset is needed and resets all the form fields to their default values. */ void reset() { for(Enumeration e=defaultValues.keys();e.hasMoreElements();) { Object input=e.nextElement(); if (input instanceof TextArea) { //catches both textareas and text input fields String defVal=(String)defaultValues.get(input); if (defVal==null) { defVal=""; } ((TextArea)input).setText(defVal); } else if (input instanceof ComboBox) { OptionItem defVal=(OptionItem)defaultValues.get(input); ComboBox combo=((ComboBox)input); if (defVal!=null) { combo.setSelectedItem(defVal); } else if (combo.size()>0) { combo.setSelectedIndex(0); } } } for (Enumeration e=defaultCheckedButtons.elements();e.hasMoreElements();) { Button b = (Button)e.nextElement(); if (!b.isSelected()) { setButton(b, true); } } for (Enumeration e=defaultUncheckedButtons.elements();e.hasMoreElements();) { Button b = (Button)e.nextElement(); if (b.isSelected()) { setButton(b, false); } } } /** * A convenience method used in reset() * * @param button The button to set (CheckBox/RadioButton) * @param checked true to check, false to uncheck */ private void setButton(Button button,boolean checked) { if (button instanceof RadioButton) { ((RadioButton)button).setSelected(checked); } else { ((CheckBox)button).setSelected(checked); } } } /** * A simple class adding the option to change the name of a command * * @author Ofir Leitner */ class NamedCommand extends Command { HTMLForm htmlForm; String name; boolean isSubmit; NamedCommand(String name,HTMLForm htmlForm,boolean isSubmit) { super(name); this.htmlForm=htmlForm; this.name=name; this.isSubmit=isSubmit; } public void actionPerformed(ActionEvent evt) { super.actionPerformed(evt); if (isSubmit) { htmlForm.submit(); } else { htmlForm.reset(); } } public void setCommandName(String name) { this.name=name; } public String getCommandName() { return name; } }