Back to index

lightning-sunbird  0.9+nobinonly
nsHTMLOptionElement.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *   Mats Palmgren <mats.palmgren@bredband.net>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 #include "nsIDOMHTMLOptionElement.h"
00040 #include "nsIDOMNSHTMLOptionElement.h"
00041 #include "nsIOptionElement.h"
00042 #include "nsIDOMHTMLOptGroupElement.h"
00043 #include "nsIDOMHTMLFormElement.h"
00044 #include "nsIDOMEventReceiver.h"
00045 #include "nsGenericHTMLElement.h"
00046 #include "nsHTMLAtoms.h"
00047 #include "nsStyleConsts.h"
00048 #include "nsPresContext.h"
00049 #include "nsIFormControl.h"
00050 #include "nsIForm.h"
00051 #include "nsIDOMText.h"
00052 #include "nsITextContent.h"
00053 #include "nsIDOMNode.h"
00054 #include "nsGenericElement.h"
00055 #include "nsIDOMHTMLCollection.h"
00056 #include "nsIJSNativeInitializer.h"
00057 #include "nsISelectElement.h"
00058 #include "nsISelectControlFrame.h"
00059 
00060 // Notify/query select frame for selected state
00061 #include "nsIFormControlFrame.h"
00062 #include "nsIDocument.h"
00063 #include "nsIPresShell.h"
00064 #include "nsIFrame.h"
00065 #include "nsIDOMHTMLSelectElement.h"
00066 #include "nsNodeInfoManager.h"
00067 #include "nsCOMPtr.h"
00068 #include "nsLayoutAtoms.h"
00069 #include "nsIEventStateManager.h"
00070 #include "nsIDocument.h"
00071 #include "nsIDOMDocument.h"
00072 
00076 class nsHTMLOptionElement : public nsGenericHTMLElement,
00077                             public nsIDOMHTMLOptionElement,
00078                             public nsIDOMNSHTMLOptionElement,
00079                             public nsIJSNativeInitializer,
00080                             public nsIOptionElement
00081 {
00082 public:
00083   nsHTMLOptionElement(nsINodeInfo *aNodeInfo);
00084   virtual ~nsHTMLOptionElement();
00085 
00086   // nsISupports
00087   NS_DECL_ISUPPORTS_INHERITED
00088 
00089   // nsIDOMNode
00090   NS_FORWARD_NSIDOMNODE_NO_CLONENODE(nsGenericHTMLElement::)
00091 
00092   // nsIDOMElement
00093   NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLElement::)
00094 
00095   // nsIDOMHTMLElement
00096   NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
00097 
00098   // nsIDOMHTMLOptionElement
00099   NS_DECL_NSIDOMHTMLOPTIONELEMENT
00100 
00101   // nsIDOMNSHTMLOptionElement
00102   NS_IMETHOD SetText(const nsAString & aText); 
00103 
00104   // nsIJSNativeInitializer
00105   NS_IMETHOD Initialize(JSContext* aContext, JSObject *aObj, 
00106                         PRUint32 argc, jsval *argv);
00107 
00108   virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
00109                                               PRInt32 aModType) const;
00110 
00111   // nsIOptionElement
00112   NS_IMETHOD SetSelectedInternal(PRBool aValue, PRBool aNotify);
00113 
00114   // nsIContent
00115   virtual PRInt32 IntrinsicState() const;
00116 
00117   nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
00118                    const nsAString& aValue, PRBool aNotify)
00119   {
00120     return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify);
00121   }
00122   virtual nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
00123                            nsIAtom* aPrefix, const nsAString& aValue,
00124                            PRBool aNotify)
00125   {
00126     nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
00127                                                 aValue, aNotify);
00128 
00129     AfterSetAttr(aNameSpaceID, aName, &aValue, aNotify);
00130     return rv;
00131   }
00132 
00133   virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
00134                              PRBool aNotify)
00135   {
00136     nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
00137                                                   aNotify);
00138 
00139     AfterSetAttr(aNameSpaceID, aAttribute, nsnull, aNotify);
00140     return rv;
00141   }
00142 
00143 protected:
00148   nsIFormControlFrame *GetSelectFrame() const;
00149 
00156   void GetSelect(nsIDOMHTMLSelectElement **aSelectElement) const;
00157 
00161   void AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
00162                     const nsAString* aValue, PRBool aNotify);
00163   PRPackedBool mIsInitialized;
00164   PRPackedBool mIsSelected;
00165 };
00166 
00167 nsGenericHTMLElement*
00168 NS_NewHTMLOptionElement(nsINodeInfo *aNodeInfo, PRBool aFromParser)
00169 {
00170   /*
00171    * nsHTMLOptionElement's will be created without a nsINodeInfo passed in
00172    * if someone says "var opt = new Option();" in JavaScript, in a case like
00173    * that we request the nsINodeInfo from the document's nodeinfo list.
00174    */
00175   nsresult rv;
00176   nsCOMPtr<nsINodeInfo> nodeInfo(aNodeInfo);
00177   if (!nodeInfo) {
00178     nsCOMPtr<nsIDocument> doc =
00179       do_QueryInterface(nsContentUtils::GetDocumentFromCaller());
00180     NS_ENSURE_TRUE(doc, nsnull);
00181 
00182     rv = doc->NodeInfoManager()->GetNodeInfo(nsHTMLAtoms::option, nsnull,
00183                                              kNameSpaceID_None,
00184                                              getter_AddRefs(nodeInfo));
00185     NS_ENSURE_SUCCESS(rv, nsnull);
00186   }
00187 
00188   return new nsHTMLOptionElement(nodeInfo);
00189 }
00190 
00191 nsHTMLOptionElement::nsHTMLOptionElement(nsINodeInfo *aNodeInfo)
00192   : nsGenericHTMLElement(aNodeInfo),
00193     mIsInitialized(PR_FALSE),
00194     mIsSelected(PR_FALSE)
00195 {
00196 }
00197 
00198 nsHTMLOptionElement::~nsHTMLOptionElement()
00199 {
00200 }
00201 
00202 // ISupports
00203 
00204 
00205 NS_IMPL_ADDREF_INHERITED(nsHTMLOptionElement, nsGenericElement)
00206 NS_IMPL_RELEASE_INHERITED(nsHTMLOptionElement, nsGenericElement)
00207 
00208 
00209 // QueryInterface implementation for nsHTMLOptionElement
00210 NS_HTML_CONTENT_INTERFACE_MAP_BEGIN(nsHTMLOptionElement, nsGenericHTMLElement)
00211   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLOptionElement)
00212   NS_INTERFACE_MAP_ENTRY(nsIDOMNSHTMLOptionElement)
00213   NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer)
00214   NS_INTERFACE_MAP_ENTRY(nsIOptionElement)
00215   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(HTMLOptionElement)
00216 NS_HTML_CONTENT_INTERFACE_MAP_END
00217 
00218 
00219 NS_IMPL_DOM_CLONENODE(nsHTMLOptionElement)
00220 
00221 
00222 NS_IMETHODIMP
00223 nsHTMLOptionElement::GetForm(nsIDOMHTMLFormElement** aForm)
00224 {
00225   NS_ENSURE_ARG_POINTER(aForm);
00226   *aForm = nsnull;
00227 
00228   nsCOMPtr<nsIDOMHTMLSelectElement> selectElement;
00229   GetSelect(getter_AddRefs(selectElement));
00230 
00231   nsCOMPtr<nsIFormControl> selectControl(do_QueryInterface(selectElement));
00232 
00233   if (selectControl) {
00234     selectControl->GetForm(aForm);
00235   }
00236 
00237   return NS_OK;
00238 }
00239 
00240 NS_IMETHODIMP
00241 nsHTMLOptionElement::SetSelectedInternal(PRBool aValue, PRBool aNotify)
00242 {
00243   mIsInitialized = PR_TRUE;
00244   mIsSelected = aValue;
00245 
00246   if (aNotify) {
00247     nsIDocument* document = GetCurrentDoc();
00248     if (document) {
00249       mozAutoDocUpdate(document, UPDATE_CONTENT_STATE, aNotify);
00250       document->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_CHECKED);
00251     }
00252   }
00253 
00254   return NS_OK;
00255 }
00256 
00257 NS_IMETHODIMP
00258 nsHTMLOptionElement::SetValue(const nsAString& aValue)
00259 {
00260   SetAttr(kNameSpaceID_None, nsHTMLAtoms::value, aValue, PR_TRUE);
00261   return NS_OK;
00262 }
00263 
00264 NS_IMETHODIMP
00265 nsHTMLOptionElement::GetValue(nsAString& aValue)
00266 {
00267   nsresult rv = GetAttr(kNameSpaceID_None, nsHTMLAtoms::value, aValue);
00268   // If the value attr is there, that is *exactly* what we use.  If it is
00269   // not, we compress whitespace .text.
00270   if (NS_CONTENT_ATTR_NOT_THERE == rv) {
00271     GetText(aValue);
00272   }
00273 
00274   return NS_OK;
00275 }
00276 
00277 NS_IMETHODIMP 
00278 nsHTMLOptionElement::GetSelected(PRBool* aValue)
00279 {
00280   NS_ENSURE_ARG_POINTER(aValue);
00281   *aValue = PR_FALSE;
00282 
00283   // If it's not initialized, initialize it.
00284   if (!mIsInitialized) {
00285     mIsInitialized = PR_TRUE;
00286     PRBool selected;
00287     GetDefaultSelected(&selected);
00288     // This does not need to be SetSelected (which sets selected in the select)
00289     // because we *will* be initialized when we are placed into a select.  Plus
00290     // it seems like that's just inviting an infinite loop.
00291     // We can pass |aNotify == PR_FALSE| since |GetSelected| is called
00292     // from |nsHTMLSelectElement::InsertOptionsIntoList|, which is
00293     // guaranteed to be called before frames are created for the
00294     // content.
00295     SetSelectedInternal(selected, PR_FALSE);
00296   }
00297 
00298   *aValue = mIsSelected;
00299   return NS_OK;
00300 }
00301 
00302 NS_IMETHODIMP
00303 nsHTMLOptionElement::SetSelected(PRBool aValue)
00304 {
00305   // Note: The select content obj maintains all the PresState
00306   // so defer to it to get the answer
00307   nsCOMPtr<nsIDOMHTMLSelectElement> selectElement;
00308   GetSelect(getter_AddRefs(selectElement));
00309   nsCOMPtr<nsISelectElement> selectInt(do_QueryInterface(selectElement));
00310   if (selectInt) {
00311     PRInt32 index;
00312     GetIndex(&index);
00313     // This should end up calling SetSelectedInternal
00314     return selectInt->SetOptionsSelectedByIndex(index, index, aValue,
00315                                                 PR_FALSE, PR_TRUE, PR_TRUE,
00316                                                 nsnull);
00317   } else {
00318     return SetSelectedInternal(aValue, PR_TRUE);
00319   }
00320 
00321   return NS_OK;
00322 }
00323 
00324 NS_IMPL_BOOL_ATTR(nsHTMLOptionElement, DefaultSelected, selected)
00325 NS_IMPL_STRING_ATTR(nsHTMLOptionElement, Label, label)
00326 //NS_IMPL_STRING_ATTR(nsHTMLOptionElement, Value, value)
00327 NS_IMPL_BOOL_ATTR(nsHTMLOptionElement, Disabled, disabled)
00328 
00329 NS_IMETHODIMP 
00330 nsHTMLOptionElement::GetIndex(PRInt32* aIndex)
00331 {
00332   NS_ENSURE_ARG_POINTER(aIndex);
00333 
00334   *aIndex = -1; // -1 indicates the index was not found
00335 
00336   // Get our containing select content object.
00337   nsCOMPtr<nsIDOMHTMLSelectElement> selectElement;
00338 
00339   GetSelect(getter_AddRefs(selectElement));
00340 
00341   if (selectElement) {
00342     // Get the options from the select object.
00343     nsCOMPtr<nsIDOMHTMLOptionsCollection> options;
00344     selectElement->GetOptions(getter_AddRefs(options));
00345 
00346     if (options) {
00347       // Walk the options to find out where we are in the list (ick, O(n))
00348       PRUint32 length = 0;
00349       options->GetLength(&length);
00350 
00351       nsCOMPtr<nsIDOMNode> thisOption;
00352 
00353       for (PRUint32 i = 0; i < length; i++) {
00354         options->Item(i, getter_AddRefs(thisOption));
00355 
00356         if (thisOption.get() == NS_STATIC_CAST(nsIDOMNode *, this)) {
00357           *aIndex = i;
00358 
00359           break;
00360         }
00361       }
00362     }
00363   }
00364 
00365   return NS_OK;
00366 }
00367 
00368 nsChangeHint
00369 nsHTMLOptionElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
00370                                             PRInt32 aModType) const
00371 {
00372   nsChangeHint retval =
00373       nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
00374 
00375   if (aAttribute == nsHTMLAtoms::label ||
00376       aAttribute == nsHTMLAtoms::text) {
00377     NS_UpdateHint(retval, NS_STYLE_HINT_REFLOW);
00378   }
00379   return retval;
00380 }
00381 
00382 NS_IMETHODIMP
00383 nsHTMLOptionElement::GetText(nsAString& aText)
00384 {
00385   PRUint32 i, numNodes = GetChildCount();
00386 
00387   aText.Truncate();
00388 
00389   nsAutoString text;
00390   for (i = 0; i < numNodes; i++) {
00391     nsCOMPtr<nsIDOMText> domText(do_QueryInterface(GetChildAt(i)));
00392 
00393     if (domText) {
00394       nsresult rv = domText->GetData(text);
00395       if (NS_FAILED(rv)) {
00396         aText.Truncate();
00397         return rv;
00398       }
00399 
00400       aText.Append(text);
00401     }
00402   }
00403 
00404   // XXX No CompressWhitespace for nsAString.  Sad.
00405   text = aText;
00406   text.CompressWhitespace(PR_TRUE, PR_TRUE);
00407   aText = text;
00408 
00409   return NS_OK;
00410 }
00411 
00412 NS_IMETHODIMP
00413 nsHTMLOptionElement::SetText(const nsAString& aText)
00414 {
00415   PRUint32 i, numNodes = GetChildCount();
00416   PRBool usedExistingTextNode = PR_FALSE;  // Do we need to create a text node?
00417   nsresult rv = NS_OK;
00418 
00419   for (i = 0; i < numNodes; i++) {
00420     nsCOMPtr<nsIDOMText> domText(do_QueryInterface(GetChildAt(i)));
00421 
00422     if (domText) {
00423       rv = domText->SetData(aText);
00424 
00425       if (NS_SUCCEEDED(rv)) {
00426         usedExistingTextNode = PR_TRUE;
00427       }
00428 
00429       break;
00430     }
00431   }
00432 
00433   if (!usedExistingTextNode) {
00434     nsCOMPtr<nsITextContent> text;
00435     rv = NS_NewTextNode(getter_AddRefs(text), mNodeInfo->NodeInfoManager());
00436     NS_ENSURE_SUCCESS(rv, rv);
00437 
00438     text->SetText(aText, PR_TRUE);
00439 
00440     rv = AppendChildTo(text, PR_TRUE);
00441   }
00442 
00443   return rv;
00444 }
00445 
00446 PRInt32
00447 nsHTMLOptionElement::IntrinsicState() const
00448 {
00449   PRInt32 state = nsGenericHTMLElement::IntrinsicState();
00450   // Nasty hack because we need to call an interface method, and one that
00451   // toggles some of our hidden internal state at that!  Would that we could
00452   // use |mutable|.
00453   PRBool selected;
00454   NS_CONST_CAST(nsHTMLOptionElement*, this)->GetSelected(&selected);
00455   if (selected) {
00456     state |= NS_EVENT_STATE_CHECKED;
00457   }
00458 
00459   PRBool disabled;
00460   GetBoolAttr(nsHTMLAtoms::disabled, &disabled);
00461   if (disabled) {
00462     state |= NS_EVENT_STATE_DISABLED;
00463     state &= ~NS_EVENT_STATE_ENABLED;
00464   } else {
00465     state &= ~NS_EVENT_STATE_DISABLED;
00466     state |= NS_EVENT_STATE_ENABLED;
00467   }
00468 
00469   return state;
00470 }
00471 
00472 // Options don't have frames - get the select content node
00473 // then call nsGenericHTMLElement::GetFormControlFrameFor()
00474 
00475 nsIFormControlFrame *
00476 nsHTMLOptionElement::GetSelectFrame() const
00477 {
00478   if (!GetParent()) {
00479     return nsnull;
00480   }
00481 
00482   nsIDocument* currentDoc = GetCurrentDoc();
00483   if (!currentDoc) {
00484     return nsnull;
00485   }
00486 
00487   nsCOMPtr<nsIDOMHTMLSelectElement> selectElement;
00488 
00489   GetSelect(getter_AddRefs(selectElement));
00490 
00491   nsCOMPtr<nsIContent> selectContent(do_QueryInterface(selectElement));
00492 
00493   if (!selectContent) {
00494     return nsnull;
00495   }
00496 
00497   return GetFormControlFrameFor(selectContent, currentDoc, PR_FALSE);
00498 }
00499 
00500 // Get the select content element that contains this option
00501 void
00502 nsHTMLOptionElement::GetSelect(nsIDOMHTMLSelectElement **aSelectElement) const
00503 {
00504   *aSelectElement = nsnull;
00505 
00506   for (nsIContent* parent = GetParent(); parent; parent = parent->GetParent()) {
00507     CallQueryInterface(parent, aSelectElement);
00508     if (*aSelectElement) {
00509       break;
00510     }
00511   }
00512 }
00513 
00514 void
00515 nsHTMLOptionElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
00516                                   const nsAString* aValue, PRBool aNotify)
00517 {
00518   if (aNotify && aNameSpaceID == kNameSpaceID_None &&
00519       aName == nsHTMLAtoms::disabled) {
00520     nsIDocument* document = GetCurrentDoc();
00521     if (document) {
00522       mozAutoDocUpdate(document, UPDATE_CONTENT_STATE, PR_TRUE);
00523       document->ContentStatesChanged(this, nsnull, NS_EVENT_STATE_DISABLED |
00524                                      NS_EVENT_STATE_ENABLED);
00525     }
00526   }
00527 }
00528 
00529 NS_IMETHODIMP    
00530 nsHTMLOptionElement::Initialize(JSContext* aContext, 
00531                                 JSObject *aObj,
00532                                 PRUint32 argc, 
00533                                 jsval *argv)
00534 {
00535   nsresult result = NS_OK;
00536 
00537   if (argc > 0) {
00538     // The first (optional) parameter is the text of the option
00539     JSString* jsstr = JS_ValueToString(aContext, argv[0]);
00540     if (jsstr) {
00541       // Create a new text node and append it to the option
00542       nsCOMPtr<nsITextContent> textContent;
00543       result = NS_NewTextNode(getter_AddRefs(textContent),
00544                               mNodeInfo->NodeInfoManager());
00545       if (NS_FAILED(result)) {
00546         return result;
00547       }
00548 
00549       textContent->SetText(NS_REINTERPRET_CAST(const PRUnichar*,
00550                                                JS_GetStringChars(jsstr)),
00551                            JS_GetStringLength(jsstr),
00552                            PR_FALSE);
00553       
00554       result = AppendChildTo(textContent, PR_FALSE);
00555       if (NS_FAILED(result)) {
00556         return result;
00557       }
00558     }
00559 
00560     if (argc > 1) {
00561       // The second (optional) parameter is the value of the option
00562       jsstr = JS_ValueToString(aContext, argv[1]);
00563       if (nsnull != jsstr) {
00564         // Set the value attribute for this element
00565         nsAutoString value(NS_REINTERPRET_CAST(const PRUnichar*,
00566                                                JS_GetStringChars(jsstr)));
00567 
00568         result = SetAttr(kNameSpaceID_None, nsHTMLAtoms::value, value,
00569                          PR_FALSE);
00570         if (NS_FAILED(result)) {
00571           return result;
00572         }
00573       }
00574 
00575       if (argc > 2) {
00576         // The third (optional) parameter is the defaultSelected value
00577         JSBool defaultSelected;
00578         if ((JS_TRUE == JS_ValueToBoolean(aContext,
00579                                           argv[2],
00580                                           &defaultSelected)) &&
00581             (JS_TRUE == defaultSelected)) {
00582           result = SetAttr(kNameSpaceID_None, nsHTMLAtoms::selected,
00583                            EmptyString(), PR_FALSE);
00584           NS_ENSURE_SUCCESS(result, result);
00585         }
00586 
00587         // XXX This is *untested* behavior.  Should work though.
00588         if (argc > 3) {
00589           JSBool selected;
00590           if (JS_TRUE == JS_ValueToBoolean(aContext,
00591                                            argv[3],
00592                                            &selected)) {
00593             return SetSelected(selected);
00594           }
00595         }
00596       }
00597     }
00598   }
00599 
00600   return result;
00601 }