0001: /*
0002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * 1. Redistributions of source code must retain the above copyright notice,
0008: * this list of conditions and the following disclaimer.
0009: * 2. Redistributions in binary form must reproduce the above copyright notice,
0010: * this list of conditions and the following disclaimer in the documentation
0011: * and/or other materials provided with the distribution.
0012: * 3. The end-user documentation included with the redistribution, if any, must
0013: * include the following acknowledgment:
0014: *
0015: * "This product includes software developed by Gargoyle Software Inc.
0016: * (http://www.GargoyleSoftware.com/)."
0017: *
0018: * Alternately, this acknowledgment may appear in the software itself, if
0019: * and wherever such third-party acknowledgments normally appear.
0020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
0021: * products derived from this software without prior written permission.
0022: * For written permission, please contact info@GargoyleSoftware.com.
0023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
0024: * "HtmlUnit" appear in their name, without prior written permission of
0025: * Gargoyle Software Inc.
0026: *
0027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
0028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
0029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
0030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
0031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
0033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
0036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0037: */
0038: package com.gargoylesoftware.htmlunit.html;
0039:
0040: import java.io.IOException;
0041: import java.io.PrintWriter;
0042: import java.io.StringWriter;
0043: import java.util.ArrayList;
0044: import java.util.Collections;
0045: import java.util.HashMap;
0046: import java.util.Iterator;
0047: import java.util.List;
0048: import java.util.Map;
0049: import java.util.NoSuchElementException;
0050: import java.util.Set;
0051:
0052: import org.apache.commons.collections.map.ListOrderedMap;
0053: import org.apache.commons.lang.ClassUtils;
0054: import org.apache.commons.lang.StringEscapeUtils;
0055: import org.mozilla.javascript.BaseFunction;
0056: import org.mozilla.javascript.Context;
0057: import org.mozilla.javascript.ContextAction;
0058: import org.mozilla.javascript.Function;
0059:
0060: import com.gargoylesoftware.htmlunit.Assert;
0061: import com.gargoylesoftware.htmlunit.ElementNotFoundException;
0062: import com.gargoylesoftware.htmlunit.Page;
0063: import com.gargoylesoftware.htmlunit.ScriptResult;
0064: import com.gargoylesoftware.htmlunit.javascript.host.Event;
0065: import com.gargoylesoftware.htmlunit.javascript.host.EventHandler;
0066: import com.gargoylesoftware.htmlunit.javascript.host.HTMLElement;
0067: import com.gargoylesoftware.htmlunit.javascript.host.MouseEvent;
0068:
0069: /**
0070: * An abstract wrapper for html elements
0071: *
0072: * @version $Revision: 2149 $
0073: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
0074: * @author <a href="mailto:gudujarlson@sf.net">Mike J. Bresnahan</a>
0075: * @author David K. Taylor
0076: * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
0077: * @author David D. Kilzer
0078: * @author Mike Gallaher
0079: * @author Denis N. Antonioli
0080: * @author Marc Guillemot
0081: * @author Ahmed Ashour
0082: */
0083: public abstract class HtmlElement extends DomElement {
0084:
0085: /** Constant meaning that the specified attribute was not defined. */
0086: public static final String ATTRIBUTE_NOT_DEFINED = new String("");
0087:
0088: /** Constant meaning that the specified attribute was found but its value was empty. */
0089: public static final String ATTRIBUTE_VALUE_EMPTY = new String("");
0090:
0091: /**
0092: * Constant indicating that a tab index value is out of bounds (less than <tt>0</tt> or greater
0093: * than <tt>32767</tt>).
0094: *
0095: * @see #getTabIndex()
0096: */
0097: public static final Short TAB_INDEX_OUT_OF_BOUNDS = new Short(
0098: Short.MIN_VALUE);
0099:
0100: /** The map holding the attributes, keyed by name. */
0101: private Map attributes_;
0102:
0103: /** The map holding the namespaces, keyed by URI. */
0104: private Map namespaces_ = new HashMap();
0105:
0106: private List/* HtmlAttributeChangeListener */attributeListeners_;
0107:
0108: /**
0109: * Creates an instance.
0110: *
0111: * @param namespaceURI the URI that identifies an XML namespace.
0112: * @param qualifiedName The qualified name of the element type to instantiate
0113: * @param htmlPage The page that contains this element
0114: * @param attributes a map ready initialized with the attributes for this element, or
0115: * <code>null</code>. The map will be stored as is, not copied.
0116: */
0117: protected HtmlElement(final String namespaceURI,
0118: final String qualifiedName, final HtmlPage htmlPage,
0119: final Map attributes) {
0120: super (namespaceURI, qualifiedName, htmlPage);
0121: if (attributes != null) {
0122: attributes_ = upgradeAttributes(htmlPage, attributes);
0123: // The HtmlAttr objects are created before the HtmlElement, so we need to go set the
0124: // parent HtmlElement, now. Also index the namespaces while we are at it.
0125: final Iterator entryIterator = attributes_.values()
0126: .iterator();
0127: while (entryIterator.hasNext()) {
0128: final HtmlAttr entry = (HtmlAttr) entryIterator.next();
0129: entry.setParentNode(this );
0130: final String attrNamespaceURI = entry.getNamespaceURI();
0131: if (attrNamespaceURI != null) {
0132: namespaces_
0133: .put(attrNamespaceURI, entry.getPrefix());
0134: }
0135: }
0136: } else {
0137: attributes_ = Collections.EMPTY_MAP;
0138: }
0139: }
0140:
0141: /**
0142: * Convert old attribute Map<String, String> to Map<String, HtmlAttr> to support backwards
0143: * compatibility.
0144: * @param attributes old attributes to be upgraded
0145: * @return upgraded attribute map
0146: */
0147: private Map upgradeAttributes(final HtmlPage htmlPage,
0148: final Map attributes) {
0149: Map upgradedAttributes = attributes;
0150: if (attributes != null && attributes.size() > 0) {
0151: final Object firstValue = attributes.values().iterator()
0152: .next();
0153: if (firstValue instanceof String) {
0154: upgradedAttributes = createAttributeMap(attributes
0155: .size());
0156: final Iterator entryIterator = attributes.entrySet()
0157: .iterator();
0158: while (entryIterator.hasNext()) {
0159: final Map.Entry entry = (Map.Entry) entryIterator
0160: .next();
0161: addAttributeToMap(htmlPage, upgradedAttributes,
0162: null, (String) entry.getKey(),
0163: (String) entry.getValue());
0164: }
0165: }
0166: }
0167: return upgradedAttributes;
0168: }
0169:
0170: /**
0171: * Overrides {@link DomNode#cloneNode(boolean)} so clone gets its own Map of attributes.
0172: * {@inheritDoc}
0173: * @deprecated This method conflicts with the W3C DOM API since the return values are
0174: * different. Use {@link #cloneDomNode(boolean)} instead.
0175: */
0176: public DomNode cloneNode(final boolean deep) {
0177: return cloneDomNode(deep);
0178: }
0179:
0180: /**
0181: * Overrides {@link DomNode#cloneDomNode(boolean)} so clone gets its own Map of attributes.
0182: * {@inheritDoc}
0183: */
0184: public DomNode cloneDomNode(final boolean deep) {
0185: final HtmlElement newNode = (HtmlElement) super
0186: .cloneDomNode(deep);
0187: final Set keySet = attributes_.keySet();
0188: newNode.attributes_ = createAttributeMap(keySet.size());
0189: for (final Iterator it = keySet.iterator(); it.hasNext();) {
0190: final Object key = it.next();
0191: final HtmlAttr attr = (HtmlAttr) attributes_.get(key);
0192: newNode.setAttributeValue(attr.getNamespaceURI(), attr
0193: .getQualifiedName(), attr.getNodeValue());
0194: }
0195: return newNode;
0196: }
0197:
0198: /**
0199: * Return the value of the attribute specified by name or an empty string. If the
0200: * result is an empty string then it will be either {@link #ATTRIBUTE_NOT_DEFINED}
0201: * if the attribute wasn't specified or {@link #ATTRIBUTE_VALUE_EMPTY} if the
0202: * attribute was specified but it was empty.
0203: *
0204: * @param attributeName the name of the attribute
0205: * @return The value of the attribute or {@link #ATTRIBUTE_NOT_DEFINED}
0206: * or {@link #ATTRIBUTE_VALUE_EMPTY}
0207: */
0208: public final String getAttribute(final String attributeName) {
0209: return getAttributeValue(attributeName);
0210: }
0211:
0212: /**
0213: * Return the qualified name (prefix:local) for the namespace and local name.
0214: *
0215: * @param namespaceURI the URI that identifies an XML namespace.
0216: * @param localName The name within the namespace.
0217: * @return The qualified name or just local name if the namespace is not fully defined.
0218: */
0219: private String getQualifiedName(final String namespaceURI,
0220: final String localName) {
0221: final String qualifiedName;
0222: if (namespaceURI != null) {
0223: final String prefix = (String) namespaces_
0224: .get(namespaceURI);
0225: if (prefix != null) {
0226: qualifiedName = prefix + ':' + localName;
0227: } else {
0228: qualifiedName = localName;
0229: }
0230: } else {
0231: qualifiedName = localName;
0232: }
0233: return qualifiedName;
0234: }
0235:
0236: /**
0237: * Return the value of the attribute specified by namespace and local name or an empty
0238: * string. If the result is an empty string then it will be either {@link #ATTRIBUTE_NOT_DEFINED}
0239: * if the attribute wasn't specified or {@link #ATTRIBUTE_VALUE_EMPTY} if the
0240: * attribute was specified but it was empty.
0241: *
0242: * @param namespaceURI the URI that identifies an XML namespace.
0243: * @param localName The name within the namespace.
0244: * @return The value of the attribute or {@link #ATTRIBUTE_NOT_DEFINED}
0245: * or {@link #ATTRIBUTE_VALUE_EMPTY}
0246: */
0247: public final String getAttributeNS(final String namespaceURI,
0248: final String localName) {
0249: return getAttributeValue(getQualifiedName(namespaceURI,
0250: localName));
0251: }
0252:
0253: /**
0254: * {@inheritDoc}
0255: */
0256: public boolean hasAttributes() {
0257: return attributes_.size() > 0;
0258: }
0259:
0260: /**
0261: * Return whether the attribute specified by name has a value.
0262: *
0263: * @param attributeName the name of the attribute
0264: * @return true if an attribute with the given name is specified on this element or has a
0265: * default value, false otherwise.
0266: */
0267: public final boolean hasAttribute(final String attributeName) {
0268: return attributes_.get(attributeName) != null;
0269: }
0270:
0271: /**
0272: * Return whether the attribute specified by namespace and local name has a value.
0273: *
0274: * @param namespaceURI the URI that identifies an XML namespace.
0275: * @param localName The name within the namespace.
0276: * @return true if an attribute with the given name is specified on this element or has a
0277: * default value, false otherwise.
0278: */
0279: public final boolean hasAttributeNS(final String namespaceURI,
0280: final String localName) {
0281: return attributes_
0282: .get(getQualifiedName(namespaceURI, localName)) != null;
0283: }
0284:
0285: /**
0286: * Return the value of the specified attribute or an empty string. If the
0287: * result is an empty string then it will be either {@link #ATTRIBUTE_NOT_DEFINED}
0288: * if the attribute wasn't specified or {@link #ATTRIBUTE_VALUE_EMPTY} if the
0289: * attribute was specified but it was empty.
0290: *
0291: * @param attributeName the name of the attribute
0292: * @return The value of the attribute or {@link #ATTRIBUTE_NOT_DEFINED}
0293: * or {@link #ATTRIBUTE_VALUE_EMPTY}
0294: */
0295: public final String getAttributeValue(final String attributeName) {
0296: final HtmlAttr attr = (HtmlAttr) attributes_.get(attributeName
0297: .toLowerCase());
0298:
0299: if (attr != null) {
0300: return attr.getNodeValue();
0301: } else {
0302: return ATTRIBUTE_NOT_DEFINED;
0303: }
0304: }
0305:
0306: /**
0307: * Set the value of the attribute specified by name.
0308: *
0309: * @param attributeName the name of the attribute
0310: * @param attributeValue The value of the attribute
0311: */
0312: public final void setAttribute(final String attributeName,
0313: final String attributeValue) {
0314: setAttributeValue(null, attributeName, attributeValue);
0315: }
0316:
0317: /**
0318: * Set the value of the attribute specified by namespace and qualified name.
0319: *
0320: * @param namespaceURI the URI that identifies an XML namespace.
0321: * @param qualifiedName The qualified name (prefix:local) of the attribute.
0322: * @param attributeValue The value of the attribute
0323: */
0324: public final void setAttributeNS(final String namespaceURI,
0325: final String qualifiedName, final String attributeValue) {
0326: setAttributeValue(namespaceURI, qualifiedName, attributeValue);
0327: }
0328:
0329: /**
0330: * Set the value of the specified attribute.
0331: *
0332: * @param attributeName the name of the attribute
0333: * @param attributeValue The value of the attribute
0334: */
0335: public final void setAttributeValue(final String attributeName,
0336: final String attributeValue) {
0337: setAttributeValue(null, attributeName, attributeValue);
0338: }
0339:
0340: /**
0341: * Set the value of the specified attribute.
0342: *
0343: * @param namespaceURI the URI that identifies an XML namespace.
0344: * @param qualifiedName The qualified name of the attribute
0345: * @param attributeValue The value of the attribute
0346: */
0347: public void setAttributeValue(final String namespaceURI,
0348: final String qualifiedName, final String attributeValue) {
0349: final String oldAttributeValue = getAttributeValue(qualifiedName);
0350: String value = attributeValue;
0351:
0352: if (attributes_ == Collections.EMPTY_MAP) {
0353: attributes_ = createAttributeMap(1);
0354: }
0355: if (value.length() == 0) {
0356: value = ATTRIBUTE_VALUE_EMPTY;
0357: }
0358:
0359: getPage().removeMappedElement(this );
0360: final HtmlAttr newAttr = addAttributeToMap(getPage(),
0361: attributes_, namespaceURI, qualifiedName.toLowerCase(),
0362: value);
0363: if (namespaceURI != null) {
0364: namespaces_.put(namespaceURI, newAttr.getPrefix());
0365: }
0366: getPage().addMappedElement(this );
0367:
0368: final HtmlAttributeChangeEvent event;
0369: if (oldAttributeValue == ATTRIBUTE_NOT_DEFINED) {
0370: event = new HtmlAttributeChangeEvent(this , qualifiedName,
0371: attributeValue);
0372: } else {
0373: event = new HtmlAttributeChangeEvent(this , qualifiedName,
0374: oldAttributeValue);
0375: }
0376:
0377: if (oldAttributeValue == ATTRIBUTE_NOT_DEFINED) {
0378: fireHtmlAttributeAdded(event);
0379: getPage().fireHtmlAttributeAdded(event);
0380: } else {
0381: fireHtmlAttributeReplaced(event);
0382: getPage().fireHtmlAttributeReplaced(event);
0383: }
0384: }
0385:
0386: /**
0387: * Removes an attribute specified by name from this element.
0388: * @param attributeName the attribute attributeName
0389: */
0390: public final void removeAttribute(final String attributeName) {
0391: final String value = getAttributeValue(attributeName);
0392:
0393: getPage().removeMappedElement(this );
0394: attributes_.remove(attributeName.toLowerCase());
0395: getPage().addMappedElement(this );
0396:
0397: final HtmlAttributeChangeEvent event = new HtmlAttributeChangeEvent(
0398: this , attributeName, value);
0399: fireHtmlAttributeRemoved(event);
0400: getPage().fireHtmlAttributeRemoved(event);
0401: }
0402:
0403: /**
0404: * Removes an attribute specified by namespace and local name from this element.
0405: * @param namespaceURI the URI that identifies an XML namespace.
0406: * @param localName The name within the namespace.
0407: */
0408: public final void removeAttributeNS(final String namespaceURI,
0409: final String localName) {
0410: removeAttribute(getQualifiedName(namespaceURI, localName));
0411: }
0412:
0413: /**
0414: * Support for reporting HTML attribute changes.
0415: * This method can be called when an attribute has been added and it will send the
0416: * appropriate HtmlAttributeChangeEvent to any registered HtmlAttributeChangeListener.
0417: *
0418: * Note that this methods recursively calls this parent fireHtmlAttributeAdded.
0419: *
0420: * @param event The event.
0421: */
0422: protected void fireHtmlAttributeAdded(
0423: final HtmlAttributeChangeEvent event) {
0424: if (attributeListeners_ != null) {
0425: synchronized (this ) {
0426: for (final Iterator iterator = attributeListeners_
0427: .iterator(); iterator.hasNext();) {
0428: ((HtmlAttributeChangeListener) iterator.next())
0429: .attributeAdded(event);
0430: }
0431: }
0432: }
0433: final DomNode parentNode = getParentDomNode();
0434: if (parentNode instanceof HtmlElement) {
0435: ((HtmlElement) parentNode).fireHtmlAttributeAdded(event);
0436: }
0437: }
0438:
0439: /**
0440: * Support for reporting html attribute changes.
0441: * This method can be called when an attribute has been replaced and it will send the
0442: * appropriate HtmlAttributeChangeEvent to any registered HtmlAttributeChangeListener.
0443: *
0444: * Note that this methods recursively calls this parent fireHtmlAttributeReplaced.
0445: *
0446: * @param event The event.
0447: */
0448: protected void fireHtmlAttributeReplaced(
0449: final HtmlAttributeChangeEvent event) {
0450: if (attributeListeners_ != null) {
0451: synchronized (this ) {
0452: for (final Iterator iterator = attributeListeners_
0453: .iterator(); iterator.hasNext();) {
0454: ((HtmlAttributeChangeListener) iterator.next())
0455: .attributeReplaced(event);
0456: }
0457: }
0458: }
0459: final DomNode parentNode = getParentDomNode();
0460: if (parentNode instanceof HtmlElement) {
0461: ((HtmlElement) parentNode).fireHtmlAttributeReplaced(event);
0462: }
0463: }
0464:
0465: /**
0466: * Support for reporting html attribute changes.
0467: * This method can be called when an attribute has been removed and it will send the
0468: * appropriate HtmlAttributeChangeEvent to any registered HtmlAttributeChangeListener.
0469: *
0470: * Note that this methods recursively calls this parent fireHtmlAttributeRemoved.
0471: *
0472: * @param event The event.
0473: */
0474: protected void fireHtmlAttributeRemoved(
0475: final HtmlAttributeChangeEvent event) {
0476: if (attributeListeners_ != null) {
0477: synchronized (this ) {
0478: for (final Iterator iterator = attributeListeners_
0479: .iterator(); iterator.hasNext();) {
0480: ((HtmlAttributeChangeListener) iterator.next())
0481: .attributeRemoved(event);
0482: }
0483: }
0484: }
0485: final DomNode parentNode = getParentDomNode();
0486: if (parentNode instanceof HtmlElement) {
0487: ((HtmlElement) parentNode).fireHtmlAttributeRemoved(event);
0488: }
0489: }
0490:
0491: /**
0492: * Return true if the specified attribute has been defined. This is neccessary
0493: * in order to distinguish between an attribute that is set to an empty string
0494: * and one that was not defined at all.
0495: *
0496: * @param attributeName The attribute to check
0497: * @return true if the attribute is defined
0498: */
0499: public boolean isAttributeDefined(final String attributeName) {
0500: return attributes_.get(attributeName.toLowerCase()) != null;
0501: }
0502:
0503: /**
0504: * @return an iterator over the {@link HtmlAttr} objects representing the
0505: * attributes of this element. Each entry holds a string key and a string value.
0506: * The elements are ordered as found in the html source code.
0507: */
0508: public Iterator getAttributeEntriesIterator() {
0509: return attributes_.values().iterator();
0510: }
0511:
0512: /**
0513: * Return the tag name of this element. The tag name is the actual html name. For example
0514: * the tag name for HtmlAnchor is "a" and the tag name for HtmlTable is "table".
0515: * This tag name will always be in lowercase, no matter what case was used in the original
0516: * document, when no namespace is defined.
0517: *
0518: * @return the tag name of this element.
0519: */
0520: public String getTagName() {
0521: if (getNamespaceURI() == null) {
0522: return getLocalName().toLowerCase();
0523: } else {
0524: return getQualifiedName();
0525: }
0526: }
0527:
0528: /** @return the node type */
0529: public short getNodeType() {
0530: return org.w3c.dom.Node.ELEMENT_NODE;
0531: }
0532:
0533: /**
0534: * @return The same value as returned by {@link #getTagName()},
0535: */
0536: public String getNodeName() {
0537: return getTagName();
0538: }
0539:
0540: /**
0541: * @return the identifier of this element.
0542: */
0543: public final String getId() {
0544: return getAttributeValue("id");
0545: }
0546:
0547: /**
0548: * Set the identifier this element.
0549: *
0550: * @param newId The new identifier of this element.
0551: */
0552: public final void setId(final String newId) {
0553: setAttributeValue("id", newId);
0554: }
0555:
0556: /**
0557: * Returns this element's tab index, if it has one. If the tab index is outside of the
0558: * valid range (less than <tt>0</tt> or greater than <tt>32767</tt>), this method
0559: * returns {@link #TAB_INDEX_OUT_OF_BOUNDS}. If this element does not have
0560: * a tab index, or its tab index is otherwise invalid, this method returns <tt>null</tt>.
0561: *
0562: * @return this element's tab index
0563: */
0564: public Short getTabIndex() {
0565: final String index = getAttributeValue("tabindex");
0566: if (index == null || index.length() == 0) {
0567: return null;
0568: }
0569: try {
0570: final long l = Long.parseLong(index);
0571: if (l >= 0 && l <= Short.MAX_VALUE) {
0572: return new Short(new Long(l).shortValue());
0573: } else {
0574: return TAB_INDEX_OUT_OF_BOUNDS;
0575: }
0576: } catch (final NumberFormatException e) {
0577: return null;
0578: }
0579: }
0580:
0581: /**
0582: * Return the element with the given name that enclosed this element or null if this element is
0583: * no such element is found.
0584: * @param tagName the name of the tag searched (case insensitive)
0585: * @return See above
0586: */
0587: public HtmlElement getEnclosingElement(final String tagName) {
0588: final String tagNameLC = tagName.toLowerCase();
0589:
0590: DomNode currentNode = getParentDomNode();
0591: while (currentNode != null) {
0592: if (currentNode instanceof HtmlElement
0593: && currentNode.getNodeName().equals(tagNameLC)) {
0594:
0595: return (HtmlElement) currentNode;
0596: }
0597: currentNode = currentNode.getParentDomNode();
0598: }
0599: return null;
0600: }
0601:
0602: /**
0603: * Return the form that enclosed this element or null if this element is not within a form.
0604: *
0605: * @return See above
0606: */
0607: public HtmlForm getEnclosingForm() {
0608: return (HtmlForm) getEnclosingElement("form");
0609: }
0610:
0611: /**
0612: * Return the form that enclosed this element or throw an exception if this element is not within a form.
0613: *
0614: * @return See above
0615: * @throws IllegalStateException If the element is not within a form.
0616: */
0617: public HtmlForm getEnclosingFormOrDie()
0618: throws IllegalStateException {
0619: final HtmlForm form = getEnclosingForm();
0620: if (form == null) {
0621: throw new IllegalStateException(
0622: "Element is not contained within a form: " + this );
0623: }
0624:
0625: return form;
0626: }
0627:
0628: /**
0629: * Simulate pressing a key on this element
0630: *
0631: * @param keyCode the key you wish to press
0632: * @deprecated use {@link #type(char)} instead
0633: */
0634: public void keyDown(final int keyCode) {
0635: keyDown(keyCode, false, false, false);
0636: }
0637:
0638: /**
0639: * Simulate pressing a key on this element
0640: *
0641: * @param keyCode the key you wish to press
0642: * @param shiftKey true if SHIFT is pressed
0643: * @param ctrlKey true if CTRL is pressed
0644: * @param altKey true if ALT is pressed
0645: * @deprecated use {@link type(char, boolean, boolean, boolean)} instead
0646: */
0647: public void keyDown(final int keyCode, final boolean shiftKey,
0648: final boolean ctrlKey, final boolean altKey) {
0649: if (this instanceof DisabledElement
0650: && ((DisabledElement) this ).isDisabled()) {
0651: return;
0652: }
0653: fireEvent(new Event(this , Event.TYPE_KEY_DOWN, keyCode,
0654: shiftKey, ctrlKey, altKey));
0655: }
0656:
0657: /**
0658: * Simulates typing the specified text while this element has focus.
0659: * @param text the text you with to simulate typing
0660: * @exception IOException If an IO error occurs
0661: */
0662: public void type(final String text) throws IOException {
0663: for (int i = 0; i < text.length(); i++) {
0664: type(text.charAt(i));
0665: }
0666: }
0667:
0668: /**
0669: * Simulates typing the specified text while this element has focus.
0670: * @param text the text you with to simulate typing
0671: * @param shiftKey true if SHIFT is pressed
0672: * @param ctrlKey true if CTRL is pressed
0673: * @param altKey true if ALT is pressed
0674: * @exception IOException If an IO error occurs
0675: */
0676: public void type(final String text, final boolean shiftKey,
0677: final boolean ctrlKey, final boolean altKey)
0678: throws IOException {
0679: for (int i = 0; i < text.length(); i++) {
0680: type(text.charAt(i), shiftKey, ctrlKey, altKey);
0681: }
0682: }
0683:
0684: /**
0685: * Simulates typing the specified character while this element has focus.
0686: * @param c the character you with to simulate typing
0687: * @return The page that occupies this window after typing.
0688: * It may be the same window or it may be a freshly loaded one.
0689: * @exception IOException If an IO error occurs
0690: */
0691: public Page type(final char c) throws IOException {
0692: return type(c, false, false, false);
0693: }
0694:
0695: /**
0696: * Simulates typing the specified character while this element has focus.
0697: * Note that for some elements, typing '\n' submits the enclosed form.
0698: * @param c the character you with to simulate typing
0699: * @param shiftKey true if SHIFT is pressed
0700: * @param ctrlKey true if CTRL is pressed
0701: * @param altKey true if ALT is pressed
0702: * @return The page that occupies this window after typing.
0703: * It may be the same window or it may be a freshly loaded one.
0704: * @exception IOException If an IO error occurs
0705: */
0706: public Page type(final char c, final boolean shiftKey,
0707: final boolean ctrlKey, final boolean altKey)
0708: throws IOException {
0709: if (this instanceof DisabledElement
0710: && ((DisabledElement) this ).isDisabled()) {
0711: return getPage();
0712: }
0713: fireEvent(new Event(this , Event.TYPE_KEY_DOWN, c, shiftKey,
0714: ctrlKey, altKey));
0715: fireEvent(new Event(this , Event.TYPE_KEY_PRESS, c, shiftKey,
0716: ctrlKey, altKey));
0717: fireEvent(new Event(this , Event.TYPE_KEY_UP, c, shiftKey,
0718: ctrlKey, altKey));
0719:
0720: final HtmlForm form = getEnclosingForm();
0721: if (form != null && c == '\n' && isSubmittableByEnter()) {
0722: return form.submit((SubmittableElement) this );
0723: } else {
0724: return getPage();
0725: }
0726: }
0727:
0728: /**
0729: * Returns true if clicking Enter (ASCII 10, or '\n') should submit the enclosed form (if any).
0730: * Default implementation is false.
0731: * @return true if clicking Enter should submit the enclosed form (if any).
0732: */
0733: protected boolean isSubmittableByEnter() {
0734: return false;
0735: }
0736:
0737: /**
0738: * recursively write the XML data for the node tree starting at <code>node</code>
0739: *
0740: * @param indent white space to indent child nodes
0741: * @param printWriter writer where child nodes are written
0742: */
0743: protected void printXml(final String indent,
0744: final PrintWriter printWriter) {
0745: final boolean hasChildren = (getFirstDomChild() != null);
0746: printWriter.print(indent + "<");
0747: printOpeningTagContentAsXml(printWriter);
0748:
0749: if (!hasChildren && !isEmptyXmlTagExpanded()) {
0750: printWriter.println("/>");
0751: } else {
0752: printWriter.println(">");
0753: printChildrenAsXml(indent, printWriter);
0754: printWriter.println(indent + "</" + getTagName() + ">");
0755: }
0756: }
0757:
0758: /**
0759: * Indicates if a node without children should be written in expanded form as xml
0760: * (i.e. with closing tag rather than with "/>")
0761: * @return <code>false</code>
0762: */
0763: protected boolean isEmptyXmlTagExpanded() {
0764: return false;
0765: }
0766:
0767: /**
0768: * Prints the content between "<" and ">" (or "/>") in the output of the tag name
0769: * and its attributes in xml format.
0770: * @param printWriter the writer to print in
0771: */
0772: protected void printOpeningTagContentAsXml(
0773: final PrintWriter printWriter) {
0774: printWriter.print(getTagName());
0775:
0776: for (final Iterator it = attributes_.keySet().iterator(); it
0777: .hasNext();) {
0778: printWriter.print(" ");
0779: final String name = (String) it.next();
0780: printWriter.print(name);
0781: printWriter.print("=\"");
0782: printWriter.print(StringEscapeUtils
0783: .escapeXml(((HtmlAttr) attributes_.get(name))
0784: .getNodeValue()));
0785: printWriter.print("\"");
0786: }
0787: }
0788:
0789: /**
0790: * Return a string representation of this object
0791: *
0792: * @return See above
0793: */
0794: public String toString() {
0795: final StringBuffer buffer = new StringBuffer();
0796:
0797: buffer.append(ClassUtils.getShortClassName(getClass()));
0798: buffer.append("[<");
0799:
0800: final StringWriter writer = new StringWriter();
0801: final PrintWriter printWriter = new PrintWriter(writer);
0802: printOpeningTagContentAsXml(printWriter);
0803: buffer.append(writer.toString());
0804:
0805: buffer.append(">]");
0806:
0807: return buffer.toString();
0808: }
0809:
0810: /**
0811: * Throw an exception. This is a convenience during development only - it
0812: * will likely be removed in the future.
0813: */
0814: protected final void notImplemented() {
0815: throw new RuntimeException("Not implemented yet");
0816: }
0817:
0818: /**
0819: * Assert that the specified string is not empty. Throw an exception if it is.
0820: *
0821: * @param description The description to pass into the exception if this string is empty
0822: * @param string The string to check
0823: * @throws IllegalArgumentException If the string is empty
0824: */
0825: protected final void assertNotEmpty(final String description,
0826: final String string) throws IllegalArgumentException {
0827:
0828: if (string.length() == 0) {
0829: throw new IllegalArgumentException(
0830: "String may not be empty: " + description);
0831: }
0832: }
0833:
0834: /**
0835: * Search by the specified criteria and return the first HtmlElement that is found
0836: *
0837: * @param elementName The name of the element
0838: * @param attributeName The name of the attribute
0839: * @param attributeValue The value of the attribute
0840: * @return The HtmlElement
0841: * @exception ElementNotFoundException If a particular xml element could not be found in the dom model
0842: */
0843: public final HtmlElement getOneHtmlElementByAttribute(
0844: final String elementName, final String attributeName,
0845: final String attributeValue)
0846: throws ElementNotFoundException {
0847:
0848: Assert.notNull("elementName", elementName);
0849: Assert.notNull("attributeName", attributeName);
0850: Assert.notNull("attributeValue", attributeValue);
0851:
0852: final List list = getHtmlElementsByAttribute(elementName,
0853: attributeName, attributeValue);
0854: final int listSize = list.size();
0855: if (listSize == 0) {
0856: throw new ElementNotFoundException(elementName,
0857: attributeName, attributeValue);
0858: }
0859:
0860: return (HtmlElement) list.get(0);
0861: }
0862:
0863: /**
0864: * Return the html element with the specified id. If more than one element
0865: * has this id (not allowed by the html spec) then return the first one.
0866: *
0867: * @param id The id value to search by
0868: * @return The html element found
0869: * @exception ElementNotFoundException If no element was found that matches the id
0870: */
0871: public HtmlElement getHtmlElementById(final String id)
0872: throws ElementNotFoundException {
0873:
0874: return getPage().getHtmlElementById(id);
0875: }
0876:
0877: /**
0878: * Return true if there is a element with the specified id. This method
0879: * is intended for situations where it is enough to know whether a specific
0880: * element is present in the document.<p>
0881: *
0882: * Implementation note: This method calls getHtmlElementById() internally
0883: * so writing code like the following would be extremely inefficient.
0884: * <pre>
0885: * if (hasHtmlElementWithId(id)) {
0886: * HtmlElement element = getHtmlElementWithId(id)
0887: * ...
0888: * }
0889: * </pre>
0890: *
0891: * @param id The id to search by
0892: * @return true if an element was found with the specified id.
0893: */
0894: public boolean hasHtmlElementWithId(final String id) {
0895: try {
0896: getHtmlElementById(id);
0897: return true;
0898: } catch (final ElementNotFoundException e) {
0899: return false;
0900: }
0901: }
0902:
0903: /**
0904: * Search by the specified criteria and return all the HtmlElement that are found
0905: *
0906: * @param elementName The name of the element
0907: * @param attributeName The name of the attribute
0908: * @param attributeValue The value of the attribute
0909: * @return A list of HtmlElements
0910: */
0911: public final List getHtmlElementsByAttribute(
0912: final String elementName, final String attributeName,
0913: final String attributeValue) {
0914:
0915: final List list = new ArrayList();
0916: final DescendantElementsIterator iterator = new DescendantElementsIterator();
0917: final String lowerCaseTagName = elementName.toLowerCase();
0918:
0919: while (iterator.hasNext()) {
0920: final HtmlElement next = iterator.nextElement();
0921: if (next.getTagName().equals(lowerCaseTagName)) {
0922: final String attValue = next
0923: .getAttributeValue(attributeName);
0924: if (attValue != null && attValue.equals(attributeValue)) {
0925: list.add(next);
0926: }
0927: }
0928: }
0929: return list;
0930: }
0931:
0932: /**
0933: * Given a list of tag names, return the html elements that correspond to any matching element
0934: *
0935: * @param acceptableTagNames The list of tag names to search by.
0936: * @return The list of tag names
0937: */
0938: public final List getHtmlElementsByTagNames(
0939: final List acceptableTagNames) {
0940: final List list = new ArrayList();
0941: final Iterator iterator = acceptableTagNames.iterator();
0942:
0943: while (iterator.hasNext()) {
0944: final String next = iterator.next().toString()
0945: .toLowerCase();
0946: list.addAll(getHtmlElementsByTagName(next));
0947: }
0948: return list;
0949: }
0950:
0951: /**
0952: * Given a list of tag names, return the html elements that correspond to any matching element
0953: *
0954: * @param tagName the tag name to match
0955: * @return The list of tag names
0956: */
0957: public final List getHtmlElementsByTagName(final String tagName) {
0958: final List list = new ArrayList();
0959: final DescendantElementsIterator iterator = new DescendantElementsIterator();
0960: final String lowerCaseTagName = tagName.toLowerCase();
0961:
0962: while (iterator.hasNext()) {
0963: final HtmlElement next = iterator.nextElement();
0964: if (lowerCaseTagName.equals(next.getTagName())) {
0965: list.add(next);
0966: }
0967: }
0968: return list;
0969: }
0970:
0971: /**
0972: * Appends a child element to this HTML element with the specified tag name
0973: * if this HTML element does not already have a child with that tag name.
0974: * Returns the appended child element, or the first existent child element
0975: * with the specified tag name if none was appended.
0976: * @param tagName the tag name of the child to append
0977: * @return the added child, or the first existing child if none was added
0978: */
0979: public final HtmlElement appendChildIfNoneExists(
0980: final String tagName) {
0981: final HtmlElement child;
0982: final List children = getHtmlElementsByTagName(tagName);
0983: if (children.isEmpty()) {
0984: // Add a new child and return it.
0985: child = getPage().createHtmlElement(tagName);
0986: appendDomChild(child);
0987: } else {
0988: // Return the first existing child.
0989: child = (HtmlElement) children.get(0);
0990: }
0991: return child;
0992: }
0993:
0994: /**
0995: * Removes the <tt>i</tt>th child element with the specified tag name
0996: * from all relationships, if possible.
0997: * @param tagName the tag name of the child to remove
0998: * @param i the index of the child to remove
0999: */
1000: public final void removeChild(final String tagName, final int i) {
1001: final List children = getHtmlElementsByTagName(tagName);
1002: if (i >= 0 && i < children.size()) {
1003: final HtmlElement child = (HtmlElement) children.get(i);
1004: child.remove();
1005: }
1006: }
1007:
1008: /**
1009: * @return an iterator over the HtmlElement children of this object, i.e. excluding the
1010: * non-element nodes
1011: */
1012: public final Iterator getChildElementsIterator() {
1013: return new ChildElementsIterator();
1014: }
1015:
1016: /**
1017: * an iterator over the HtmlElement children
1018: */
1019: protected class ChildElementsIterator implements Iterator {
1020:
1021: private HtmlElement nextElement_;
1022:
1023: /** constructor */
1024: public ChildElementsIterator() {
1025: if (getFirstDomChild() != null) {
1026: if (getFirstDomChild() instanceof HtmlElement) {
1027: nextElement_ = (HtmlElement) getFirstDomChild();
1028: } else {
1029: setNextElement(getFirstDomChild());
1030: }
1031: }
1032: }
1033:
1034: /** @return is there a next one ? */
1035: public boolean hasNext() {
1036: return nextElement_ != null;
1037: }
1038:
1039: /** @return the next one */
1040: public Object next() {
1041: return nextElement();
1042: }
1043:
1044: /** remove the current one */
1045: public void remove() {
1046: if (nextElement_ == null) {
1047: throw new IllegalStateException();
1048: }
1049: final DomNode sibling = nextElement_
1050: .getPreviousDomSibling();
1051: if (sibling != null) {
1052: sibling.remove();
1053: }
1054: }
1055:
1056: /** @return the next element */
1057: public HtmlElement nextElement() {
1058: if (nextElement_ != null) {
1059: final HtmlElement result = nextElement_;
1060: setNextElement(nextElement_);
1061: return result;
1062: } else {
1063: throw new NoSuchElementException();
1064: }
1065: }
1066:
1067: private void setNextElement(final DomNode node) {
1068: DomNode next = node.getNextDomSibling();
1069: while (next != null && !(next instanceof HtmlElement)) {
1070: next = next.getNextDomSibling();
1071: }
1072: nextElement_ = (HtmlElement) next;
1073: }
1074: }
1075:
1076: /**
1077: * Converts an iteration of plain {@link java.util.Map.Entry} into an iteration of {@link HtmlAttr}.
1078: * @deprecated This class is no longer used since attributes are now represented by HtmlAttr.
1079: * @author Denis N. Antonioli
1080: */
1081: public static class MapEntryWrappingIterator implements Iterator {
1082: /**
1083: * The original Iterator on the attribute map.
1084: */
1085: private final Iterator baseIter_;
1086:
1087: /**
1088: * Wraps a new iterator around an iterator of attributes.
1089: *
1090: * @param iterator An iterator of Map.Entry.
1091: * @param htmlElement the Parent of all the attributes.
1092: */
1093: public MapEntryWrappingIterator(final Iterator iterator,
1094: final HtmlElement htmlElement) {
1095: baseIter_ = iterator;
1096: }
1097:
1098: /**
1099: * Delegates to wrapped Iterator.
1100: *
1101: * @return true if the iterator has more elements.
1102: */
1103: public boolean hasNext() {
1104: return baseIter_.hasNext();
1105: }
1106:
1107: /**
1108: * Wraps the next entry into a new HtmlAttr.
1109: *
1110: * @return Next entry.
1111: */
1112: public Object next() {
1113: return ((Map.Entry) baseIter_.next()).getValue();
1114: }
1115:
1116: /**
1117: * Delegates to wrapped Iterator.
1118: */
1119: public void remove() {
1120: baseIter_.remove();
1121: }
1122: }
1123:
1124: /**
1125: * Create an attribute map as needed by HtmlElement. This is just used by the element factories.
1126: * @param attributeCount the initial number of attributes to be added to the map.
1127: * @return the attribute map.
1128: */
1129: static Map createAttributeMap(final int attributeCount) {
1130: return ListOrderedMap.decorate(new HashMap(attributeCount)); // preserve insertion order
1131: }
1132:
1133: /**
1134: * Add an attribute to the attribute map. This is just used by the element factories.
1135: * @param attributeMap the attribute map where the attribute will be added.
1136: * @param namespaceURI the URI that identifies an XML namespace.
1137: * @param qualifiedName The qualified name of the attribute
1138: * @param value The value of the attribute
1139: */
1140: static HtmlAttr addAttributeToMap(final HtmlPage page,
1141: final Map attributeMap, final String namespaceURI,
1142: final String qualifiedName, final String value) {
1143: final HtmlAttr newAttr = new HtmlAttr(page, namespaceURI,
1144: qualifiedName, value);
1145: attributeMap.put(qualifiedName, newAttr);
1146: return newAttr;
1147: }
1148:
1149: /**
1150: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
1151: * Return a Function to be executed when a given event occurs.
1152: * @param eventName Name of event such as "onclick" or "onblur", etc.
1153: * @return A rhino javascript executable Function, or null if no event
1154: * handler has been defined
1155: */
1156: public final Function getEventHandler(final String eventName) {
1157: final HTMLElement jsObj = (HTMLElement) getScriptObject();
1158: return jsObj.getEventHandler(eventName);
1159: }
1160:
1161: /**
1162: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
1163: * Register a Function as an event handler.
1164: * @param eventName Name of event such as "onclick" or "onblur", etc.
1165: * @param eventHandler A rhino javascript executable Function
1166: */
1167: public final void setEventHandler(final String eventName,
1168: final Function eventHandler) {
1169: final HTMLElement jsObj = (HTMLElement) getScriptObject();
1170: jsObj.setEventHandler(eventName, eventHandler);
1171: }
1172:
1173: /**
1174: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
1175: * Register a snippet of javascript code as an event handler. The javascript code will
1176: * be wrapped inside a unique function declaration which provides one argument named
1177: * "event"
1178: * @param eventName Name of event such as "onclick" or "onblur", etc.
1179: * @param jsSnippet executable javascript code
1180: */
1181: public final void setEventHandler(final String eventName,
1182: final String jsSnippet) {
1183: final BaseFunction function = new EventHandler(this , eventName,
1184: jsSnippet);
1185: setEventHandler(eventName, function);
1186: getLog().debug(
1187: "Created event handler " + function.getFunctionName()
1188: + " for " + eventName + " on " + this );
1189: }
1190:
1191: /**
1192: * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br/>
1193: * Removes the specified event handler.
1194: * @param eventName Name of the event such as "onclick" or "onblur", etc.
1195: */
1196: public final void removeEventHandler(final String eventName) {
1197: setEventHandler(eventName, (Function) null);
1198: }
1199:
1200: /**
1201: * Adds an HtmlAttributeChangeListener to the listener list.
1202: * The listener is registered for all attributes of this HtmlElement,
1203: * as well as descendant elements.
1204: *
1205: * @param listener the attribute change listener to be added.
1206: * @see #removeHtmlAttributeChangeListener(HtmlAttributeChangeListener)
1207: */
1208: public void addHtmlAttributeChangeListener(
1209: final HtmlAttributeChangeListener listener) {
1210: Assert.notNull("listener", listener);
1211: synchronized (this ) {
1212: if (attributeListeners_ == null) {
1213: attributeListeners_ = new ArrayList();
1214: }
1215: attributeListeners_.add(listener);
1216: }
1217: }
1218:
1219: /**
1220: * Removes an HtmlAttributeChangeListener from the listener list.
1221: * This method should be used to remove HtmlAttributeChangeListener that were registered
1222: * for all attributes of this HtmlElement, as well as descendant elements.
1223: *
1224: * @param listener the attribute change listener to be removed.
1225: * @see #addHtmlAttributeChangeListener(HtmlAttributeChangeListener)
1226: */
1227: public void removeHtmlAttributeChangeListener(
1228: final HtmlAttributeChangeListener listener) {
1229: Assert.notNull("listener", listener);
1230: synchronized (this ) {
1231: if (attributeListeners_ != null) {
1232: attributeListeners_.remove(listener);
1233: }
1234: }
1235: }
1236:
1237: /**
1238: * Shortcut for {@link #fireEvent(Event)}.
1239: * @param eventType the event type (like "load", "click")
1240: * @return the execution result. <code>null</code> if nothing is executed.
1241: */
1242: public ScriptResult fireEvent(final String eventType) {
1243: return fireEvent(new Event(this , eventType));
1244: }
1245:
1246: /**
1247: * Fire the event on the element. Nothing is done if JavaScript is disabled
1248: * @param event the event to fire.
1249: * @return the execution result. <code>null</code> if nothing is executed.
1250: */
1251: public ScriptResult fireEvent(final Event event) {
1252: if (!getPage().getWebClient().isJavaScriptEnabled()) {
1253: return null;
1254: }
1255:
1256: getLog().debug("Firing " + event);
1257: final HTMLElement jsElt = (HTMLElement) getScriptObject();
1258: final ContextAction action = new ContextAction() {
1259: public Object run(final Context cx) {
1260: return jsElt.fireEvent(event);
1261: }
1262: };
1263:
1264: final ScriptResult result = (ScriptResult) Context.call(action);
1265: final boolean isIE = getPage().getWebClient()
1266: .getBrowserVersion().isIE();
1267: if ((!isIE && event.isPreventDefault())
1268: || (isIE && ScriptResult.isFalse(result))) {
1269: preventDefault();
1270: }
1271: return result;
1272: }
1273:
1274: /**
1275: * This method is called if the current fired event is canceled by <tt>preventDefault()</tt> in FireFox,
1276: * or by returning <tt>false</tt> in Internet Explorer.
1277: *
1278: * The default implementation does nothing.
1279: */
1280: protected void preventDefault() {
1281: }
1282:
1283: /**
1284: * Simulate moving the mouse over this element.
1285: *
1286: * @return The page that occupies this window after the mouse moves over this element.
1287: * It may be the same window or it may be a freshly loaded one.
1288: */
1289: public Page mouseOver() {
1290: return mouseOver(false, false, false, MouseEvent.BUTTON_LEFT);
1291: }
1292:
1293: /**
1294: * Simulate moving the mouse over this element.
1295: *
1296: * @param shiftKey true if SHIFT is pressed
1297: * @param ctrlKey true if CTRL is pressed
1298: * @param altKey true if ALT is pressed
1299: * @param button the button code, must be {@link MouseEvent#BUTTON_LEFT}, {@link MouseEvent#BUTTON_MIDDLE}
1300: * or {@link MouseEvent#BUTTON_RIGHT}
1301: *
1302: * @return The page that occupies this window after the mouse moves over this element.
1303: * It may be the same window or it may be a freshly loaded one.
1304: */
1305: public Page mouseOver(final boolean shiftKey,
1306: final boolean ctrlKey, final boolean altKey,
1307: final int button) {
1308: return doMouseEvent(MouseEvent.TYPE_MOUSE_OVER, shiftKey,
1309: ctrlKey, altKey, button);
1310: }
1311:
1312: /**
1313: * Simulate moving the mouse inside this element.
1314: *
1315: * @return The page that occupies this window after the mouse moves inside this element.
1316: * It may be the same window or it may be a freshly loaded one.
1317: */
1318: public Page mouseMove() {
1319: return mouseMove(false, false, false, MouseEvent.BUTTON_LEFT);
1320: }
1321:
1322: /**
1323: * Simulate moving the mouse inside this element.
1324: *
1325: * @param shiftKey true if SHIFT is pressed
1326: * @param ctrlKey true if CTRL is pressed
1327: * @param altKey true if ALT is pressed
1328: * @param button the button code, must be {@link MouseEvent#BUTTON_LEFT}, {@link MouseEvent#BUTTON_MIDDLE}
1329: * or {@link MouseEvent#BUTTON_RIGHT}
1330: *
1331: * @return The page that occupies this window after the mouse moves inside this element.
1332: * It may be the same window or it may be a freshly loaded one.
1333: */
1334: public Page mouseMove(final boolean shiftKey,
1335: final boolean ctrlKey, final boolean altKey,
1336: final int button) {
1337: return doMouseEvent(MouseEvent.TYPE_MOUSE_MOVE, shiftKey,
1338: ctrlKey, altKey, button);
1339: }
1340:
1341: /**
1342: * Simulate moving the mouse out of this element.
1343: *
1344: * @return The page that occupies this window after the mouse moves out of this element.
1345: * It may be the same window or it may be a freshly loaded one.
1346: */
1347: public Page mouseOut() {
1348: return mouseOut(false, false, false, MouseEvent.BUTTON_LEFT);
1349: }
1350:
1351: /**
1352: * Simulate moving the mouse out of this element.
1353: *
1354: * @param shiftKey true if SHIFT is pressed
1355: * @param ctrlKey true if CTRL is pressed
1356: * @param altKey true if ALT is pressed
1357: * @param button the button code, must be {@link MouseEvent#BUTTON_LEFT}, {@link MouseEvent#BUTTON_MIDDLE}
1358: * or {@link MouseEvent#BUTTON_RIGHT}
1359: *
1360: * @return The page that occupies this window after the mouse moves out of this element.
1361: * It may be the same window or it may be a freshly loaded one.
1362: */
1363: public Page mouseOut(final boolean shiftKey, final boolean ctrlKey,
1364: final boolean altKey, final int button) {
1365: return doMouseEvent(MouseEvent.TYPE_MOUSE_OUT, shiftKey,
1366: ctrlKey, altKey, button);
1367: }
1368:
1369: /**
1370: * Simulate clicking the mouse in this element.
1371: *
1372: * @return The page that occupies this window after the mouse is clicked in this element.
1373: * It may be the same window or it may be a freshly loaded one.
1374: */
1375: public Page mouseDown() {
1376: return mouseDown(false, false, false, MouseEvent.BUTTON_LEFT);
1377: }
1378:
1379: /**
1380: * Simulate clicking the mouse in this element.
1381: *
1382: * @param shiftKey true if SHIFT is pressed
1383: * @param ctrlKey true if CTRL is pressed
1384: * @param altKey true if ALT is pressed
1385: * @param button the button code, must be {@link MouseEvent#BUTTON_LEFT}, {@link MouseEvent#BUTTON_MIDDLE}
1386: * or {@link MouseEvent#BUTTON_RIGHT}
1387: *
1388: * @return The page that occupies this window after the mouse is clicked in this element.
1389: * It may be the same window or it may be a freshly loaded one.
1390: */
1391: public Page mouseDown(final boolean shiftKey,
1392: final boolean ctrlKey, final boolean altKey,
1393: final int button) {
1394: return doMouseEvent(MouseEvent.TYPE_MOUSE_DOWN, shiftKey,
1395: ctrlKey, altKey, button);
1396: }
1397:
1398: /**
1399: * Simulate releasing the mouse click in this element.
1400: *
1401: * @return The page that occupies this window after the mouse click is released in this element.
1402: * It may be the same window or it may be a freshly loaded one.
1403: */
1404: public Page mouseUp() {
1405: return mouseUp(false, false, false, MouseEvent.BUTTON_LEFT);
1406: }
1407:
1408: /**
1409: * Simulate releasing the mouse click in this element.
1410: *
1411: * @param shiftKey true if SHIFT is pressed
1412: * @param ctrlKey true if CTRL is pressed
1413: * @param altKey true if ALT is pressed
1414: * @param button the button code, must be {@link MouseEvent#BUTTON_LEFT}, {@link MouseEvent#BUTTON_MIDDLE}
1415: * or {@link MouseEvent#BUTTON_RIGHT}
1416: *
1417: * @return The page that occupies this window after the mouse click is released in this element.
1418: * It may be the same window or it may be a freshly loaded one.
1419: */
1420: public Page mouseUp(final boolean shiftKey, final boolean ctrlKey,
1421: final boolean altKey, final int button) {
1422: return doMouseEvent(MouseEvent.TYPE_MOUSE_UP, shiftKey,
1423: ctrlKey, altKey, button);
1424: }
1425:
1426: /**
1427: * Simulate the given mouse event.
1428: *
1429: * @param shiftKey true if SHIFT is pressed
1430: * @param ctrlKey true if CTRL is pressed
1431: * @param altKey true if ALT is pressed
1432: * @param button the button code, must be {@link MouseEvent#BUTTON_LEFT}, {@link MouseEvent#BUTTON_MIDDLE}
1433: * or {@link MouseEvent#BUTTON_RIGHT}
1434: *
1435: * @return The page that occupies this window after the mouse event occur on this element.
1436: * It may be the same window or it may be a freshly loaded one.
1437: */
1438: private Page doMouseEvent(final String eventType,
1439: final boolean shiftKey, final boolean ctrlKey,
1440: final boolean altKey, final int button) {
1441: if (this instanceof DisabledElement
1442: && ((DisabledElement) this ).isDisabled()) {
1443: return getPage();
1444: }
1445:
1446: final HtmlPage page = getPage();
1447: final Event event = new MouseEvent(this , eventType, shiftKey,
1448: ctrlKey, altKey, button);
1449: final ScriptResult scriptResult = fireEvent(event);
1450: final Page currentPage;
1451: if (scriptResult == null) {
1452: currentPage = page;
1453: } else {
1454: currentPage = scriptResult.getNewPage();
1455: }
1456: return currentPage;
1457: }
1458:
1459: /**
1460: * Simulate right clicking the mouse in this element.
1461: *
1462: * @return The page that occupies this window after the mouse is right clicked in this element.
1463: * It may be the same window or it may be a freshly loaded one.
1464: */
1465: public Page rightClick() {
1466: return rightClick(false, false, false);
1467: }
1468:
1469: /**
1470: * Simulate right clicking the mouse in this element.
1471: *
1472: * <p>This is equivalent to calling {@link #mouseDown(boolean, boolean, boolean, int)},
1473: * then {@link #mouseUp(boolean, boolean, boolean, int)}
1474: *
1475: * @param shiftKey true if SHIFT is pressed
1476: * @param ctrlKey true if CTRL is pressed
1477: * @param altKey true if ALT is pressed
1478: *
1479: * @return The page that occupies this window after the mouse is right clicked in this element.
1480: * It may be the same window or it may be a freshly loaded one.
1481: */
1482: public Page rightClick(final boolean shiftKey,
1483: final boolean ctrlKey, final boolean altKey) {
1484: final Page mouseDownPage = mouseDown(shiftKey, ctrlKey, altKey,
1485: MouseEvent.BUTTON_RIGHT);
1486: if (mouseDownPage != getPage()) {
1487: getLog()
1488: .debug(
1489: "rightClick() is incomplete, as mouseDown() loaded a different page.");
1490: return mouseDownPage;
1491: }
1492: final Page mouseUpPage = mouseUp(shiftKey, ctrlKey, altKey,
1493: MouseEvent.BUTTON_RIGHT);
1494: if (mouseUpPage != getPage()) {
1495: getLog()
1496: .debug(
1497: "rightClick() is incomplete, as mouseUp() loaded a different page.");
1498: return mouseUpPage;
1499: }
1500: return doMouseEvent(MouseEvent.TYPE_CONTEXT_MENU, shiftKey,
1501: ctrlKey, altKey, MouseEvent.BUTTON_RIGHT);
1502: }
1503:
1504: /**
1505: * Remove focus from this element.
1506: */
1507: public void blur() {
1508: getPage().moveFocusToElement(null);
1509: }
1510:
1511: /**
1512: * Set the focus to this element.
1513: */
1514: public void focus() {
1515: getPage().moveFocusToElement(this);
1516: }
1517: }
|