1. /*
  2. * @(#)FormView.java 1.7 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.html;
  8. import java.net.*;
  9. import java.io.*;
  10. import java.awt.*;
  11. import java.awt.event.*;
  12. import javax.swing.*;
  13. import javax.swing.event.*;
  14. import javax.swing.text.*;
  15. /**
  16. * Component decorator that implements the view interface
  17. * for form elements, <input>, <textarea>,
  18. * and <select>. The model for the component is stored
  19. * as an attribute of the the element (using StyleConstants.ModelAttribute),
  20. * and is used to build the component of the view. The type
  21. * of the model is assumed to of the type that would be set by
  22. * <code>HTMLDocument.HTMLReader.FormAction</code>. If there are
  23. * multiple views mapped over the document, they will share the
  24. * embedded component models.
  25. * <p>
  26. * The components produced get their opaque property set to
  27. * false. The following table shows what components get built
  28. * by this view.
  29. * <table>
  30. * <tr>
  31. * <th>Element Type
  32. * <th>Component built
  33. * <tr>
  34. * <td>input, type button
  35. * <td>JButton
  36. * <tr>
  37. * <td>input, type checkbox
  38. * <td>JCheckBox
  39. * <tr>
  40. * <td>input, type image
  41. * <td>JButton
  42. * <tr>
  43. * <td>input, type password
  44. * <td>JPasswordField
  45. * <tr>
  46. * <td>input, type radio
  47. * <td>JRadioButton
  48. * <tr>
  49. * <td>input, type reset
  50. * <td>JButton
  51. * <tr>
  52. * <td>input, type submit
  53. * <td>JButton
  54. * <tr>
  55. * <td>input, type text
  56. * <td>JTextField
  57. * <tr>
  58. * <td>select, size > 1 or multiple attribute defined
  59. * <td>JList in a JScrollPane
  60. * <tr>
  61. * <td>select, size unspecified or 1
  62. * <td>JComboBox
  63. * <tr>
  64. * <td>textarea
  65. * <td>JTextArea in a JScrollPane
  66. * </table>
  67. *
  68. * @author Timothy Prinzing
  69. * @author Sunita Mani
  70. * @version 1.7 11/29/01
  71. */
  72. public class FormView extends ComponentView implements ActionListener {
  73. /**
  74. * If a value attribute is not specified for a FORM input element
  75. * of type "submit" or "reset", then these default strings are used.
  76. */
  77. public static final String SUBMIT = new String("Submit Query");
  78. public static final String RESET = new String("Reset");
  79. /**
  80. * Creates a new FormView object.
  81. *
  82. * @param elem the element to decorate
  83. */
  84. public FormView(Element elem) {
  85. super(elem);
  86. }
  87. /**
  88. * Create the component. This is basically a
  89. * big switch statement based upon the tag type
  90. * and html attributes of the associated element.
  91. */
  92. protected Component createComponent() {
  93. AttributeSet attr = getElement().getAttributes();
  94. HTML.Tag t = (HTML.Tag)
  95. attr.getAttribute(StyleConstants.NameAttribute);
  96. JComponent c = null;
  97. Object model = attr.getAttribute(StyleConstants.ModelAttribute);
  98. if (t == HTML.Tag.INPUT) {
  99. c = createInputComponent(attr, model);
  100. } else if (t == HTML.Tag.SELECT) {
  101. if (model instanceof OptionListModel) {
  102. JList list = new JList((ListModel) model);
  103. int size = HTML.getIntegerAttributeValue(attr,
  104. HTML.Attribute.SIZE,
  105. 1);
  106. list.setVisibleRowCount(size);
  107. list.setOpaque(false);
  108. list.setSelectionModel((ListSelectionModel)model);
  109. c = new JScrollPane(list);
  110. } else {
  111. c = new JComboBox((ComboBoxModel) model);
  112. }
  113. } else if (t == HTML.Tag.TEXTAREA) {
  114. JTextArea area = new JTextArea((Document) model);
  115. int rows = HTML.getIntegerAttributeValue(attr,
  116. HTML.Attribute.ROWS,
  117. 0);
  118. area.setRows(rows);
  119. int cols = HTML.getIntegerAttributeValue(attr,
  120. HTML.Attribute.COLS,
  121. 0);
  122. area.setColumns(cols);
  123. c = new JScrollPane(area,
  124. JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
  125. JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
  126. }
  127. if (c != null) {
  128. c.setAlignmentY(1.0f);
  129. c.setOpaque(false);
  130. }
  131. return c;
  132. }
  133. /**
  134. * Creates a component for an <input> element based on the
  135. * value of the "type" attribute.
  136. *
  137. * @param set of attributes associated with the <input> element.
  138. * @param model the value of the StyleConstants.ModelAttribute
  139. * @return the component.
  140. */
  141. private JComponent createInputComponent(AttributeSet attr, Object model) {
  142. JComponent c = null;
  143. String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
  144. if (type.equals("submit") || type.equals("reset")) {
  145. String value = (String)
  146. attr.getAttribute(HTML.Attribute.VALUE);
  147. if (value == null) {
  148. if (type.equals("submit")) {
  149. value = SUBMIT;
  150. } else {
  151. value = RESET;
  152. }
  153. }
  154. JButton button = new JButton(value);
  155. if (model != null) {
  156. button.setModel((ButtonModel)model);
  157. button.addActionListener(this);
  158. }
  159. c = button;
  160. } else if (type.equals("image")) {
  161. String srcAtt = (String) attr.getAttribute(HTML.Attribute.SRC);
  162. JButton button;
  163. try {
  164. URL base = ((HTMLDocument)getElement().getDocument()).getBase();
  165. URL srcURL = new URL(base, srcAtt);
  166. Icon icon = new ImageIcon(srcURL);
  167. button = new JButton(icon);
  168. } catch (MalformedURLException e) {
  169. button = new JButton(srcAtt);
  170. }
  171. if (model != null) {
  172. button.setModel((ButtonModel)model);
  173. button.addMouseListener(new MouseEventListener());
  174. }
  175. c = button;
  176. } else if (type.equals("checkbox")) {
  177. c = new JCheckBox();
  178. if (model != null) {
  179. boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
  180. ((JToggleButton.ToggleButtonModel) model).setSelected(checked);
  181. ((JCheckBox)c).setModel((JToggleButton.ToggleButtonModel)model);
  182. }
  183. } else if (type.equals("radio")) {
  184. c = new JRadioButton();
  185. if (model != null) {
  186. boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null);
  187. ((JToggleButton.ToggleButtonModel)model).setSelected(checked);
  188. ((JRadioButton)c).setModel((JToggleButton.ToggleButtonModel)model);
  189. }
  190. } else if (type.equals("text")) {
  191. JTextField field = new JTextField();
  192. c = field;
  193. if (model != null) {
  194. field.setDocument((Document) model);
  195. }
  196. int size = HTML.getIntegerAttributeValue(attr,
  197. HTML.Attribute.SIZE,
  198. -1);
  199. if (size > 0) {
  200. field.setColumns(size);
  201. }
  202. String value = (String)
  203. attr.getAttribute(HTML.Attribute.VALUE);
  204. if (value != null) {
  205. field.setText(value);
  206. }
  207. field.addActionListener(this);
  208. } else if (type.equals("password")) {
  209. JPasswordField field = new JPasswordField();
  210. c = field;
  211. if (model != null) {
  212. field.setDocument((Document) model);
  213. }
  214. int size = HTML.getIntegerAttributeValue(attr,
  215. HTML.Attribute.SIZE,
  216. -1);
  217. if (size > 0) {
  218. field.setColumns(size);
  219. }
  220. String value = (String)
  221. attr.getAttribute(HTML.Attribute.VALUE);
  222. if (value != null) {
  223. field.setText(value);
  224. }
  225. field.addActionListener(this);
  226. }
  227. return c;
  228. }
  229. /**
  230. * Responsible for processeing the ActionEvent.
  231. * If the element associated with the FormView,
  232. * has a type of "submit", "reset", "text" or "password"
  233. * then the action is processed. In the case of a "submit"
  234. * the form is submitted. In the case of a "reset"
  235. * the form is reset to its original state.
  236. * In the case of "text" or "password", if the
  237. * element is the last one of type "text" or "password",
  238. * the form is submitted. Otherwise, focus is transferred
  239. * to the next component in the form.
  240. *
  241. * @param evt the ActionEvent.
  242. */
  243. public void actionPerformed(ActionEvent evt) {
  244. Element element = getElement();
  245. StringBuffer dataBuffer = new StringBuffer();
  246. HTMLDocument doc = (HTMLDocument)getDocument();
  247. AttributeSet attr = element.getAttributes();
  248. String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
  249. if (type.equals("submit")) {
  250. doc.getFormData(dataBuffer, element);
  251. submitData(dataBuffer.toString());
  252. } else if (type.equals("reset")) {
  253. doc.resetForm(element);
  254. } else if (type.equals("text") || type.equals("password")) {
  255. if (doc.isLastTextOrPasswordField(element)) {
  256. doc.getFormData(dataBuffer, element);
  257. submitData(dataBuffer.toString());
  258. } else {
  259. getComponent().transferFocus();
  260. }
  261. }
  262. }
  263. /**
  264. * This method is responsible for submitting the form data.
  265. * A thread is forked to undertake the submission.
  266. */
  267. protected void submitData(String data) {
  268. //System.err.println("data ->"+data+"<-");
  269. SubmitThread dataThread = new SubmitThread(getElement(), data);
  270. dataThread.start();
  271. }
  272. /**
  273. * The SubmitThread is responsible for submitting the form.
  274. * It performs a POST or GET based on the value of method
  275. * attribute associated with HTML.Tag.FORM. In addition to
  276. * submitting, it is also responsible for display the
  277. * results of the form submission.
  278. */
  279. class SubmitThread extends Thread {
  280. String data;
  281. HTMLDocument hdoc;
  282. HTMLDocument newDoc;
  283. AttributeSet formAttr;
  284. InputStream in;
  285. public SubmitThread(Element elem, String data) {
  286. this.data = data;
  287. hdoc = (HTMLDocument)elem.getDocument();
  288. formAttr = hdoc.getFormAttributes(elem.getAttributes());
  289. }
  290. /**
  291. * This method is responsible for extracting the
  292. * method and action attributes associated with the
  293. * <form> and using those to determine how (i.e POST or GET)
  294. * and where(URL) to submit the form. If action is
  295. * not specified, the base url of the existing document is
  296. * used. Also, if method is not specified, the default is
  297. * GET. Once form submission is done, run uses the
  298. * SwingUtilities.invokeLater() method, to load the results
  299. * of the form submission into the current JEditorPane.
  300. */
  301. public void run() {
  302. if (data.length() > 0) {
  303. String method = getMethod();
  304. String action = getAction();
  305. URL url;
  306. try {
  307. URL actionURL;
  308. /* if action is null use the base url and ensure that
  309. the file name excludes any parameters that may be attached */
  310. URL baseURL = hdoc.getBase();
  311. if (action == null) {
  312. String file = baseURL.getFile();
  313. int paramIndex = file.indexOf('?');
  314. if (paramIndex >= 0) {
  315. file = file.substring(0, paramIndex);
  316. }
  317. actionURL = new URL(baseURL.getProtocol(),
  318. baseURL.getHost(),
  319. baseURL.getPort(),
  320. file);
  321. } else {
  322. actionURL = new URL(baseURL, action);
  323. }
  324. URLConnection connection;
  325. if ("post".equals(method)) {
  326. url = actionURL;
  327. connection = url.openConnection();
  328. postData(connection, data);
  329. } else {
  330. /* the default, GET */
  331. url = new URL(actionURL+"?"+data);
  332. connection = url.openConnection();
  333. }
  334. in = connection.getInputStream();
  335. // safe assumption since we are in an html document
  336. JEditorPane c = (JEditorPane)getContainer();
  337. HTMLEditorKit kit = (HTMLEditorKit)c.getEditorKit();
  338. newDoc = (HTMLDocument)kit.createDefaultDocument();
  339. newDoc.putProperty(Document.StreamDescriptionProperty, url);
  340. Runnable callLoadDocument = new Runnable() {
  341. public void run() {
  342. loadDocument();
  343. }
  344. };
  345. SwingUtilities.invokeLater(callLoadDocument);
  346. } catch (MalformedURLException m) {
  347. // REMIND how do we deal with exceptions ??
  348. } catch (IOException e) {
  349. // REMIND how do we deal with exceptions ??
  350. }
  351. }
  352. }
  353. /**
  354. * This method is responsible for loading the
  355. * document into the FormView's container,
  356. * i.e JEditorPane.
  357. */
  358. public void loadDocument() {
  359. JEditorPane c = (JEditorPane)getContainer();
  360. try {
  361. c.read(in, newDoc);
  362. } catch (IOException e) {
  363. // REMIND failed to load document
  364. }
  365. }
  366. /**
  367. * Get the value of the action attribute. If the value
  368. * specifies additional information at the end of the URL,
  369. * i.e after a ?, then only the URL is returned.
  370. *
  371. */
  372. public String getAction() {
  373. if (formAttr == null) {
  374. return null;
  375. }
  376. String action = (String)formAttr.getAttribute(HTML.Attribute.ACTION);
  377. // commonly occuring error
  378. int index = action.indexOf('?');
  379. if (index != -1) {
  380. action = action.substring(0, index);
  381. }
  382. return action;
  383. }
  384. /**
  385. * Get the form's method parameter.
  386. */
  387. String getMethod() {
  388. if (formAttr != null) {
  389. String method = (String)formAttr.getAttribute(HTML.Attribute.METHOD);
  390. if (method != null) {
  391. return method.toLowerCase();
  392. }
  393. }
  394. return null;
  395. }
  396. /**
  397. * This method is responsible for writing out the form submission
  398. * data when the method is POST.
  399. *
  400. * @param connection to use.
  401. * @param data to write.
  402. */
  403. public void postData(URLConnection connection, String data) {
  404. connection.setDoOutput(true);
  405. PrintWriter out = null;
  406. try {
  407. out = new PrintWriter(new OutputStreamWriter(connection.getOutputStream()));
  408. out.print(data);
  409. out.flush();
  410. } catch (IOException e) {
  411. // REMIND: should do something reasonable!
  412. } finally {
  413. if (out != null) {
  414. out.close();
  415. }
  416. }
  417. }
  418. }
  419. /**
  420. * MouseEventListener class to handle form submissions when
  421. * an input with type equal to image is clicked on.
  422. * A MouseListener is necessary since along with the image
  423. * data the coordinates associated with the mouse click
  424. * need to be submitted.
  425. */
  426. protected class MouseEventListener extends MouseAdapter {
  427. public void mouseReleased(MouseEvent evt) {
  428. String imageData = getImageData(evt.getPoint());
  429. imageSubmit(imageData);
  430. }
  431. }
  432. /**
  433. * This method is called to submit a form in response
  434. * to a click on an image. i.e an <input> form
  435. * element of type "image".
  436. *
  437. * @param the mouse click coordinates.
  438. */
  439. protected void imageSubmit(String imageData) {
  440. StringBuffer dataBuffer = new StringBuffer();
  441. Element elem = getElement();
  442. HTMLDocument hdoc = (HTMLDocument)elem.getDocument();
  443. hdoc.getFormData(dataBuffer, getElement());
  444. if (dataBuffer.length() > 0) {
  445. dataBuffer.append('&');
  446. }
  447. dataBuffer.append(imageData);
  448. submitData(dataBuffer.toString());
  449. return;
  450. }
  451. /**
  452. * Extracts the value of the name attribute
  453. * associated with the input element of type
  454. * image. If name is defined it is encoded using
  455. * the URLEncoder.encode() method and the
  456. * image data is returned in the following format:
  457. * name + ".x" +"="+ x +"&"+ name +".y"+"="+ y
  458. * otherwise,
  459. * "x="+ x +"&y="+ y
  460. *
  461. * @param point associated with the mouse click.
  462. * @return the image data.
  463. */
  464. private String getImageData(Point point) {
  465. String mouseCoords = point.x + ":" + point.y;
  466. int sep = mouseCoords.indexOf(':');
  467. String x = mouseCoords.substring(0, sep);
  468. String y = mouseCoords.substring(++sep);
  469. String name = (String) getElement().getAttributes().getAttribute(HTML.Attribute.NAME);
  470. String data;
  471. if (name == null || name.equals("")) {
  472. data = "x="+ x +"&y="+ y;
  473. } else {
  474. name = URLEncoder.encode(name);
  475. data = name + ".x" +"="+ x +"&"+ name +".y"+"="+ y;
  476. }
  477. return data;
  478. }
  479. }