2 // System.Web.UI.ClientScriptManager.cs
5 // Duncan Mak (duncan@ximian.com)
6 // Gonzalo Paniagua (gonzalo@ximian.com)
7 // Andreas Nahr (ClassDevelopment@A-SoftTech.com)
8 // Lluis Sanchez (lluis@novell.com)
10 // (C) 2002,2003 Ximian, Inc. (http://www.ximian.com)
11 // (c) 2003 Novell, Inc. (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Collections;
38 using System.Collections.Generic;
41 using System.Collections.Specialized;
42 using System.Web.Util;
43 using System.Globalization;
45 namespace System.Web.UI
52 class ClientScriptManager
54 Hashtable registeredArrayDeclares;
55 ScriptEntry clientScriptBlocks;
56 ScriptEntry startupScriptBlocks;
57 internal Hashtable hiddenFields;
58 ScriptEntry submitStatements;
59 ScriptEntry scriptIncludes;
62 int [] eventValidationValues;
63 int eventValidationPos = 0;
64 Hashtable expandoAttributes;
65 bool _hasRegisteredForEventValidationOnCallback;
68 internal ClientScriptManager (Page page)
74 public string GetPostBackClientEvent (Control control, string argument)
76 return GetPostBackEventReference (control, argument);
80 public string GetPostBackClientHyperlink (Control control, string argument)
82 return "javascript:" + GetPostBackEventReference (control, argument);
86 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
88 if (registerForEventValidation)
89 RegisterForEventValidation (control.UniqueID, argument);
90 return "javascript:" + GetPostBackEventReference (control, argument);
99 string GetPostBackEventReference (Control control, string argument)
102 throw new ArgumentNullException ("control");
104 page.RequiresPostBackScript ();
106 return String.Format ("{0}.__doPostBack('{1}','{2}')", page.theForm, control.UniqueID, argument);
108 return String.Format ("__doPostBack('{0}','{1}')", control.UniqueID, argument);
113 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
116 throw new ArgumentNullException ("control");
118 if (registerForEventValidation)
119 RegisterForEventValidation (control.UniqueID, argument);
120 return GetPostBackEventReference (control, argument);
123 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
126 throw new ArgumentNullException ("options");
127 if (registerForEventValidation)
128 RegisterForEventValidation (options);
129 return GetPostBackEventReference (options);
132 public string GetPostBackEventReference (PostBackOptions options)
135 throw new ArgumentNullException ("options");
137 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
138 !options.AutoPostBack && !options.PerformValidation)
140 if (!options.ClientSubmit)
143 if (options.RequiresJavaScriptProtocol)
144 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
146 return GetPostBackEventReference (options.TargetControl, options.Argument);
149 RegisterWebFormClientScript ();
151 string actionUrl = options.ActionUrl;
152 if (actionUrl != null)
153 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
155 if(options.TrackFocus)
156 RegisterHiddenField (Page.LastFocusID, String.Empty);
158 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
160 // Allow the page to transform ActionUrl to a portlet action url
161 if (actionUrl != null && page.PortletNamespace != null) {
162 actionUrl = page.CreateActionUrl(actionUrl);
167 return String.Format ("{0}WebForm_DoPostback({1},{2},{3},{4},{5},{6},{7},{8},{9})",
169 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID),
170 ClientScriptManager.GetScriptLiteral (options.Argument),
171 ClientScriptManager.GetScriptLiteral (actionUrl),
172 ClientScriptManager.GetScriptLiteral (options.AutoPostBack),
173 ClientScriptManager.GetScriptLiteral (options.PerformValidation),
174 ClientScriptManager.GetScriptLiteral (options.TrackFocus),
175 ClientScriptManager.GetScriptLiteral (options.ClientSubmit),
176 ClientScriptManager.GetScriptLiteral (options.ValidationGroup),
181 internal void RegisterWebFormClientScript ()
183 if (IsClientScriptIncludeRegistered (typeof (Page), "webform"))
186 RegisterClientScriptInclude (typeof (Page), "webform", GetWebResourceUrl (typeof (Page), "webform.js"));
187 page.RequiresPostBackScript ();
190 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
192 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
195 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
197 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
200 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
203 throw new ArgumentNullException ("control");
204 if(!(control is ICallbackEventHandler))
205 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
207 return GetCallbackEventReference (control.UniqueID, argument, clientCallback, context, clientErrorCallback, useAsync);
210 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
212 RegisterWebFormClientScript ();
214 return string.Format ("WebForm_DoCallback('{0}',{1},{2},{3},{4},{5},{6})", target, argument, clientCallback, context, ((clientErrorCallback == null) ? "null" : clientErrorCallback), (useAsync ? "true" : "false"), page.theForm);
223 string GetWebResourceUrl(Type type, string resourceName)
226 throw new ArgumentNullException ("type");
228 if (resourceName == null || resourceName.Length == 0)
229 throw new ArgumentNullException ("type");
231 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
235 public bool IsClientScriptBlockRegistered (string key)
237 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
240 public bool IsClientScriptBlockRegistered (Type type, string key)
242 return IsScriptRegistered (clientScriptBlocks, type, key);
245 public bool IsStartupScriptRegistered (string key)
247 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
250 public bool IsStartupScriptRegistered (Type type, string key)
252 return IsScriptRegistered (startupScriptBlocks, type, key);
255 public bool IsOnSubmitStatementRegistered (string key)
257 return IsScriptRegistered (submitStatements, GetType(), key);
260 public bool IsOnSubmitStatementRegistered (Type type, string key)
262 return IsScriptRegistered (submitStatements, type, key);
265 public bool IsClientScriptIncludeRegistered (string key)
267 return IsScriptRegistered (scriptIncludes, GetType(), key);
270 public bool IsClientScriptIncludeRegistered (Type type, string key)
272 return IsScriptRegistered (scriptIncludes, type, key);
275 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
277 while (scriptList != null) {
278 if (scriptList.Type == type && scriptList.Key == key)
280 scriptList = scriptList.Next;
285 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
287 if (registeredArrayDeclares == null)
288 registeredArrayDeclares = new Hashtable();
290 if (!registeredArrayDeclares.ContainsKey (arrayName))
291 registeredArrayDeclares.Add (arrayName, new ArrayList());
293 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
296 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
298 ScriptEntry last = null;
299 ScriptEntry entry = scriptList;
301 while (entry != null) {
302 if (entry.Type == type && entry.Key == key)
309 script = "<script type=\"text/javascript\"" +
311 "language=\"javascript\"" +
313 ">\n<!--\n" + script + "\n// -->\n</script>";
316 entry = new ScriptEntry (type, key, script);
318 if (last != null) last.Next = entry;
319 else scriptList = entry;
322 internal void RegisterClientScriptBlock (string key, string script)
324 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
327 public void RegisterClientScriptBlock (Type type, string key, string script)
329 RegisterClientScriptBlock (type, key, script, false);
332 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
335 throw new ArgumentNullException ("type");
337 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
340 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
342 if (hiddenFields == null)
343 hiddenFields = new Hashtable ();
345 if (!hiddenFields.ContainsKey (hiddenFieldName))
346 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
349 internal void RegisterOnSubmitStatement (string key, string script)
351 RegisterScript (ref submitStatements, GetType (), key, script, false);
354 public void RegisterOnSubmitStatement (Type type, string key, string script)
357 throw new ArgumentNullException ("type");
359 RegisterScript (ref submitStatements, type, key, script, false);
362 internal void RegisterStartupScript (string key, string script)
364 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
367 public void RegisterStartupScript (Type type, string key, string script)
369 RegisterStartupScript (type, key, script, false);
372 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
375 throw new ArgumentNullException ("type");
377 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
380 public void RegisterClientScriptInclude (string key, string url)
382 RegisterClientScriptInclude (GetType (), key, url);
385 public void RegisterClientScriptInclude (Type type, string key, string url)
388 throw new ArgumentNullException ("type");
389 if (url == null || url.Length == 0)
390 throw new ArgumentException ("url");
392 RegisterScript (ref scriptIncludes, type, key, url, false);
396 public void RegisterClientScriptResource (Type type, string resourceName)
398 RegisterScript (ref scriptIncludes, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), false);
401 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
403 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
406 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
408 if (controlId == null)
409 throw new ArgumentNullException ("controlId");
411 if (attributeName == null)
412 throw new ArgumentNullException ("attributeName");
414 if (expandoAttributes == null)
415 expandoAttributes = new Hashtable ();
417 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
419 list = new ListDictionary ();
420 expandoAttributes [controlId] = list;
423 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
426 private void EnsureEventValidationArray ()
428 if (eventValidationValues == null)
429 eventValidationValues = new int [64];
431 int len = eventValidationValues.Length;
433 if (eventValidationPos >= len) {
434 int [] tmp = new int [len * 2];
435 Array.Copy (eventValidationValues, tmp, len);
436 eventValidationValues = tmp;
440 internal void ResetEventValidationState ()
442 eventValidationPos = 0;
445 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
446 private int CalculateEventHash (string uniqueId, string argument)
448 int uniqueIdHash = uniqueId.GetHashCode ();
449 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
450 return (uniqueIdHash ^ argumentHash);
453 public void RegisterForEventValidation (PostBackOptions options)
455 // MS.NET does not check for options == null, so we won't too...
456 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
459 public void RegisterForEventValidation (string uniqueId)
461 RegisterForEventValidation (uniqueId, null);
464 public void RegisterForEventValidation (string uniqueId, string argument)
466 if (!page.EnableEventValidation)
468 if (uniqueId == null || uniqueId.Length == 0)
471 _hasRegisteredForEventValidationOnCallback = true;
472 else if (page.LifeCycle < PageLifeCycle.Render)
473 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
475 EnsureEventValidationArray ();
477 int hash = CalculateEventHash (uniqueId, argument);
478 for (int i = 0; i < eventValidationPos; i++)
479 if (eventValidationValues [i] == hash)
481 eventValidationValues [eventValidationPos++] = hash;
484 public void ValidateEvent (string uniqueId)
486 ValidateEvent (uniqueId, null);
489 public void ValidateEvent (string uniqueId, string argument)
491 if (uniqueId == null || uniqueId.Length == 0)
492 throw new ArgumentException ("must not be null or empty", "uniqueId");
493 if (!page.EnableEventValidation)
495 if (eventValidationValues == null)
498 int hash = CalculateEventHash (uniqueId, argument);
499 for (int i = 0; i < eventValidationValues.Length; i++)
500 if (eventValidationValues [i] == hash)
504 throw new ArgumentException ("Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation=\"true\"/> in configuration or <%@ Page EnableEventValidation=\"true\" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.");
507 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
509 while (scriptList != null) {
510 writer.WriteLine (scriptList.Script);
511 scriptList = scriptList.Next;
516 internal void RestoreEventValidationState (string fieldValue)
518 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
520 IStateFormatter fmt = page.GetFormatter ();
521 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
522 eventValidationPos = eventValidationValues.Length;
525 internal void SaveEventValidationState ()
527 if (!page.EnableEventValidation)
530 string eventValidation = GetEventValidationStateFormatted ();
531 if (eventValidation == null)
534 RegisterHiddenField (EventStateFieldName, eventValidation);
537 internal string GetEventValidationStateFormatted ()
539 if (eventValidationValues == null || eventValidationValues.Length == 0)
542 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
545 IStateFormatter fmt = page.GetFormatter ();
546 int [] array = new int [eventValidationPos];
547 Array.Copy (eventValidationValues, array, eventValidationPos);
548 return fmt.Serialize (array);
551 internal string EventStateFieldName
553 get { return "__EVENTVALIDATION"; }
556 internal void WriteExpandoAttributes (HtmlTextWriter writer)
558 if (expandoAttributes == null)
562 WriteBeginScriptBlock (writer);
564 foreach (string controlId in expandoAttributes.Keys) {
565 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
566 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
567 foreach (string attributeName in attrs.Keys) {
568 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
571 WriteEndScriptBlock (writer);
576 internal void WriteBeginScriptBlock (HtmlTextWriter writer)
578 writer.WriteLine ("<script"+
580 " language=\"javascript\""+
582 " type=\"text/javascript\">");
583 writer.WriteLine ("<!--");
586 internal void WriteEndScriptBlock (HtmlTextWriter writer)
588 writer.WriteLine ("// -->");
589 writer.WriteLine ("</script>");
592 internal void WriteHiddenFields (HtmlTextWriter writer)
594 if (hiddenFields == null)
597 writer.RenderBeginTag (HtmlTextWriterTag.Div);
598 foreach (string key in hiddenFields.Keys) {
599 string value = hiddenFields [key] as string;
600 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, value);
602 writer.RenderEndTag (); // DIV
606 internal void WriteClientScriptIncludes (HtmlTextWriter writer)
608 ScriptEntry entry = scriptIncludes;
609 while (entry != null) {
610 if (!entry.Rendered) {
612 if (!page.IsPortletRender)
614 writer.WriteLine ("\n<script src=\"{0}\" type=\"text/javascript\"></script>", entry.Script);
617 string scriptKey = "inc_" + entry.Key.GetHashCode ().ToString ("X");
618 writer.WriteLine ("\n<script type=\"text/javascript\">");
619 writer.WriteLine ("<!--");
620 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
621 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
622 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", entry.Script);
623 writer.WriteLine ("// -->");
624 writer.WriteLine ("</script>");
627 entry.Rendered = true;
633 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
635 WriteScripts (writer, clientScriptBlocks);
638 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
640 WriteScripts (writer, startupScriptBlocks);
643 internal void WriteArrayDeclares (HtmlTextWriter writer)
645 if (registeredArrayDeclares != null) {
647 WriteBeginScriptBlock (writer);
648 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
649 while (arrayEnum.MoveNext()) {
650 writer.Write("\tvar ");
651 writer.Write(arrayEnum.Key);
652 writer.Write(" = new Array(");
653 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
655 while (arrayListEnum.MoveNext()) {
660 writer.Write(arrayListEnum.Current);
662 writer.WriteLine(");");
664 // in addition, add a form array declaration
665 if (page.IsPortletRender) {
666 writer.Write ("\t" + page.theForm + ".");
667 writer.Write (arrayEnum.Key);
668 writer.Write (" = ");
669 writer.Write (arrayEnum.Key);
670 writer.WriteLine (";");
674 WriteEndScriptBlock (writer);
680 internal string GetClientValidationEvent (string validationGroup) {
681 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
683 if (page.IsPortletRender)
684 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
690 internal string GetClientValidationEvent ()
692 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
694 if (page.IsPortletRender)
695 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
701 internal string WriteSubmitStatements ()
703 if (submitStatements == null) return null;
705 StringBuilder sb = new StringBuilder ();
706 ScriptEntry entry = submitStatements;
707 while (entry != null) {
709 sb.Append (EnsureEndsWithSemicolon (entry.Script));
711 sb.Append (entry.Script);
716 RegisterClientScriptBlock ("HtmlForm-OnSubmitStatemen",
717 @"<script type=""text/javascript"">
719 " + page.theForm + @".WebForm_OnSubmit = function () {
720 " + sb.ToString () + @"
725 return "javascript:return this.WebForm_OnSubmit();";
728 return sb.ToString ();
732 [MonoTODO ("optimize s.Replace")]
733 internal static string GetScriptLiteral (object ob)
737 else if (ob is string) {
738 string s = (string)ob;
739 s = s.Replace ("\\", "\\\\");
740 s = s.Replace ("\"", "\\\"");
741 return "\"" + s + "\"";
742 } else if (ob is bool) {
743 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
745 return ob.ToString ();
753 public string Script;
754 public ScriptEntry Next;
755 public bool Rendered;
757 public ScriptEntry (Type type, string key, string script)
767 internal static string EnsureEndsWithSemicolon (string value) {
768 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')