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;
44 namespace System.Web.UI
51 class ClientScriptManager
53 Hashtable registeredArrayDeclares;
54 ScriptEntry clientScriptBlocks;
55 ScriptEntry startupScriptBlocks;
56 internal Hashtable hiddenFields;
57 ScriptEntry submitStatements;
58 ScriptEntry scriptIncludes;
61 List <int> eventValidationValues;
62 Hashtable expandoAttributes;
63 bool _hasRegisteredForEventValidationOnCallback;
66 internal ClientScriptManager (Page page)
72 public string GetPostBackClientEvent (Control control, string argument)
74 return GetPostBackEventReference (control, argument);
78 public string GetPostBackClientHyperlink (Control control, string argument)
80 return "javascript:" + GetPostBackEventReference (control, argument);
84 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
86 if (registerForEventValidation)
87 RegisterForEventValidation (control.UniqueID, argument);
88 return "javascript:" + GetPostBackEventReference (control, argument);
97 string GetPostBackEventReference (Control control, string argument)
100 throw new ArgumentNullException ("control");
102 page.RequiresPostBackScript ();
104 return String.Format ("{0}.__doPostBack('{1}','{2}')", page.theForm, control.UniqueID, argument);
106 return String.Format ("__doPostBack('{0}','{1}')", control.UniqueID, argument);
111 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
114 throw new ArgumentNullException ("control");
116 if (registerForEventValidation)
117 RegisterForEventValidation (control.UniqueID, argument);
118 return GetPostBackEventReference (control, argument);
121 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
124 throw new ArgumentNullException ("options");
125 if (registerForEventValidation)
126 RegisterForEventValidation (options);
127 return GetPostBackEventReference (options);
130 public string GetPostBackEventReference (PostBackOptions options)
133 throw new ArgumentNullException ("options");
135 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
136 !options.AutoPostBack && !options.PerformValidation)
138 if (!options.ClientSubmit)
141 if (options.RequiresJavaScriptProtocol)
142 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
144 return GetPostBackEventReference (options.TargetControl, options.Argument);
147 RegisterWebFormClientScript ();
149 string actionUrl = options.ActionUrl;
150 if (actionUrl != null)
151 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
153 if(options.TrackFocus)
154 RegisterHiddenField (Page.LastFocusID, String.Empty);
156 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
158 // Allow the page to transform ActionUrl to a portlet action url
159 if (actionUrl != null && page.PortletNamespace != null) {
160 actionUrl = page.CreateActionUrl(actionUrl);
165 return String.Format ("{0}WebForm_DoPostback({1},{2},{3},{4},{5},{6},{7},{8},{9})",
167 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID),
168 ClientScriptManager.GetScriptLiteral (options.Argument),
169 ClientScriptManager.GetScriptLiteral (actionUrl),
170 ClientScriptManager.GetScriptLiteral (options.AutoPostBack),
171 ClientScriptManager.GetScriptLiteral (options.PerformValidation),
172 ClientScriptManager.GetScriptLiteral (options.TrackFocus),
173 ClientScriptManager.GetScriptLiteral (options.ClientSubmit),
174 ClientScriptManager.GetScriptLiteral (options.ValidationGroup),
179 internal void RegisterWebFormClientScript ()
181 if (IsClientScriptIncludeRegistered (typeof (Page), "webform"))
184 RegisterClientScriptInclude (typeof (Page), "webform", GetWebResourceUrl (typeof (Page), "webform.js"));
185 page.RequiresPostBackScript ();
188 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
190 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
193 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
195 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
198 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
201 throw new ArgumentNullException ("control");
202 if(!(control is ICallbackEventHandler))
203 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
205 return GetCallbackEventReference (control.UniqueID, argument, clientCallback, context, clientErrorCallback, useAsync);
208 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
210 RegisterWebFormClientScript ();
212 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);
221 string GetWebResourceUrl(Type type, string resourceName)
224 throw new ArgumentNullException ("type");
226 if (resourceName == null || resourceName.Length == 0)
227 throw new ArgumentNullException ("type");
229 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
233 public bool IsClientScriptBlockRegistered (string key)
235 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
238 public bool IsClientScriptBlockRegistered (Type type, string key)
240 return IsScriptRegistered (clientScriptBlocks, type, key);
243 public bool IsStartupScriptRegistered (string key)
245 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
248 public bool IsStartupScriptRegistered (Type type, string key)
250 return IsScriptRegistered (startupScriptBlocks, type, key);
253 public bool IsOnSubmitStatementRegistered (string key)
255 return IsScriptRegistered (submitStatements, GetType(), key);
258 public bool IsOnSubmitStatementRegistered (Type type, string key)
260 return IsScriptRegistered (submitStatements, type, key);
263 public bool IsClientScriptIncludeRegistered (string key)
265 return IsScriptRegistered (scriptIncludes, GetType(), key);
268 public bool IsClientScriptIncludeRegistered (Type type, string key)
270 return IsScriptRegistered (scriptIncludes, type, key);
273 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
275 while (scriptList != null) {
276 if (scriptList.Type == type && scriptList.Key == key)
278 scriptList = scriptList.Next;
283 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
285 if (registeredArrayDeclares == null)
286 registeredArrayDeclares = new Hashtable();
288 if (!registeredArrayDeclares.ContainsKey (arrayName))
289 registeredArrayDeclares.Add (arrayName, new ArrayList());
291 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
294 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
296 ScriptEntry last = null;
297 ScriptEntry entry = scriptList;
299 while (entry != null) {
300 if (entry.Type == type && entry.Key == key)
307 script = "<script language=javascript>\n<!--\n" + script + "\n// -->\n</script>";
309 entry = new ScriptEntry (type, key, script);
311 if (last != null) last.Next = entry;
312 else scriptList = entry;
315 internal void RegisterClientScriptBlock (string key, string script)
317 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
320 public void RegisterClientScriptBlock (Type type, string key, string script)
322 RegisterClientScriptBlock (type, key, script, false);
325 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
328 throw new ArgumentNullException ("type");
330 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
333 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
335 if (hiddenFields == null)
336 hiddenFields = new Hashtable ();
338 if (!hiddenFields.ContainsKey (hiddenFieldName))
339 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
342 internal void RegisterOnSubmitStatement (string key, string script)
344 RegisterScript (ref submitStatements, GetType (), key, script, false);
347 public void RegisterOnSubmitStatement (Type type, string key, string script)
350 throw new ArgumentNullException ("type");
352 RegisterScript (ref submitStatements, type, key, script, false);
355 internal void RegisterStartupScript (string key, string script)
357 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
360 public void RegisterStartupScript (Type type, string key, string script)
362 RegisterStartupScript (type, key, script, false);
365 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
368 throw new ArgumentNullException ("type");
370 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
373 public void RegisterClientScriptInclude (string key, string url)
375 RegisterClientScriptInclude (GetType (), key, url);
378 public void RegisterClientScriptInclude (Type type, string key, string url)
381 throw new ArgumentNullException ("type");
382 if (url == null || url.Length == 0)
383 throw new ArgumentException ("url");
385 RegisterScript (ref scriptIncludes, type, key, url, false);
389 public void RegisterClientScriptResource (Type type, string resourceName)
391 RegisterScript (ref scriptIncludes, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), false);
394 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
396 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
399 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
401 if (controlId == null)
402 throw new ArgumentNullException ("controlId");
404 if (attributeName == null)
405 throw new ArgumentNullException ("attributeName");
407 if (expandoAttributes == null)
408 expandoAttributes = new Hashtable ();
410 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
412 list = new ListDictionary ();
413 expandoAttributes [controlId] = list;
416 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
419 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
420 private int CalculateEventHash (string uniqueId, string argument)
422 int uniqueIdHash = uniqueId.GetHashCode ();
423 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
424 return (uniqueIdHash ^ argumentHash);
427 public void RegisterForEventValidation (PostBackOptions options)
429 // MS.NET does not check for options == null, so we won't too...
430 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
433 public void RegisterForEventValidation (string uniqueId)
435 RegisterForEventValidation (uniqueId, null);
438 public void RegisterForEventValidation (string uniqueId, string argument)
440 if (!page.EnableEventValidation)
442 if (uniqueId == null || uniqueId.Length == 0)
445 _hasRegisteredForEventValidationOnCallback = true;
446 else if (page.LifeCycle < PageLifeCycle.Render)
447 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
448 if (eventValidationValues == null)
449 eventValidationValues = new List <int> ();
452 int hash = CalculateEventHash (uniqueId, argument);
453 if (eventValidationValues.BinarySearch (hash) < 0)
454 eventValidationValues.Add (hash);
457 public void ValidateEvent (string uniqueId)
459 ValidateEvent (uniqueId, null);
462 public void ValidateEvent (string uniqueId, string argument)
464 if (uniqueId == null || uniqueId.Length == 0)
465 throw new ArgumentException ("must not be null or empty", "uniqueId");
466 if (!page.EnableEventValidation)
468 if (eventValidationValues == null)
471 int hash = CalculateEventHash (uniqueId, argument);
472 if (eventValidationValues.BinarySearch (hash) < 0)
477 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.");
480 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
482 while (scriptList != null) {
483 writer.WriteLine (scriptList.Script);
484 scriptList = scriptList.Next;
489 internal void RestoreEventValidationState (string fieldValue)
491 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
493 IStateFormatter fmt = page.GetFormatter ();
494 int [] eventValues = (int []) fmt.Deserialize (fieldValue);
495 #if TARGET_JVM // FIXME: No support yet for passing 'int[]' as 'T[]'
496 eventValidationValues = new List<int> (eventValues.Length);
497 for (int i = 0; i < eventValues.Length; i++)
498 eventValidationValues.Add(eventValues[i]);
500 eventValidationValues = new List<int> (eventValues);
502 eventValidationValues.Sort ();
505 internal void SaveEventValidationState ()
507 if (!page.EnableEventValidation)
510 string eventValidation = GetEventValidationStateFormatted ();
511 if (eventValidation == null)
514 RegisterHiddenField (EventStateFieldName, eventValidation);
517 internal string GetEventValidationStateFormatted ()
519 if (eventValidationValues == null || eventValidationValues.Count == 0)
522 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
525 IStateFormatter fmt = page.GetFormatter ();
526 int [] array = new int [eventValidationValues.Count];
527 #if TARGET_JVM // FIXME: No support yet for passing 'int[]' as 'T[]'
528 ((ICollection)eventValidationValues).CopyTo (array, 0);
530 eventValidationValues.CopyTo (array);
532 return fmt.Serialize (array);
535 internal string EventStateFieldName
537 get { return "__EVENTVALIDATION"; }
540 internal void WriteExpandoAttributes (HtmlTextWriter writer)
542 if (expandoAttributes == null)
546 WriteBeginScriptBlock (writer);
548 foreach (string controlId in expandoAttributes.Keys) {
549 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
550 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
551 foreach (string attributeName in attrs.Keys) {
552 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
555 WriteEndScriptBlock (writer);
560 internal void WriteBeginScriptBlock (HtmlTextWriter writer)
562 writer.WriteLine ("<script"+
564 " language=\"javascript\""+
566 " type=\"text/javascript\">");
567 writer.WriteLine ("<!--");
570 internal void WriteEndScriptBlock (HtmlTextWriter writer)
572 writer.WriteLine ("// -->");
573 writer.WriteLine ("</script>");
576 internal void WriteHiddenFields (HtmlTextWriter writer)
578 if (hiddenFields == null)
581 foreach (string key in hiddenFields.Keys) {
582 string value = hiddenFields [key] as string;
583 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, value);
589 internal void WriteClientScriptIncludes (HtmlTextWriter writer)
591 ScriptEntry entry = scriptIncludes;
592 while (entry != null) {
593 if (!entry.Rendered) {
595 if (!page.IsPortletRender)
597 writer.WriteLine ("\n<script src=\"{0}\" type=\"text/javascript\"></script>", entry.Script);
600 string scriptKey = "inc_" + entry.Key.GetHashCode ().ToString ("X");
601 writer.WriteLine ("\n<script type=\"text/javascript\">");
602 writer.WriteLine ("<!--");
603 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
604 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
605 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", entry.Script);
606 writer.WriteLine ("// -->");
607 writer.WriteLine ("</script>");
610 entry.Rendered = true;
616 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
618 WriteScripts (writer, clientScriptBlocks);
621 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
623 WriteScripts (writer, startupScriptBlocks);
626 internal void WriteArrayDeclares (HtmlTextWriter writer)
628 if (registeredArrayDeclares != null) {
630 WriteBeginScriptBlock (writer);
631 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
632 while (arrayEnum.MoveNext()) {
633 writer.Write("\tvar ");
634 writer.Write(arrayEnum.Key);
635 writer.Write(" = new Array(");
636 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
638 while (arrayListEnum.MoveNext()) {
643 writer.Write(arrayListEnum.Current);
645 writer.WriteLine(");");
647 // in addition, add a form array declaration
648 if (page.IsPortletRender) {
649 writer.Write ("\t" + page.theForm + ".");
650 writer.Write (arrayEnum.Key);
651 writer.Write (" = ");
652 writer.Write (arrayEnum.Key);
653 writer.WriteLine (";");
657 WriteEndScriptBlock (writer);
663 internal string GetClientValidationEvent (string validationGroup) {
664 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
666 if (page.IsPortletRender)
667 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
673 internal string GetClientValidationEvent ()
675 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
677 if (page.IsPortletRender)
678 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
684 internal string WriteSubmitStatements ()
686 if (submitStatements == null) return null;
688 StringBuilder sb = new StringBuilder ();
689 ScriptEntry entry = submitStatements;
690 while (entry != null) {
692 sb.Append (EnsureEndsWithSemicolon (entry.Script));
694 sb.Append (entry.Script);
699 RegisterClientScriptBlock ("HtmlForm-OnSubmitStatemen",
700 @"<script type=""text/javascript"">
702 " + page.theForm + @".WebForm_OnSubmit = function () {
703 " + sb.ToString () + @"
708 return "javascript:return this.WebForm_OnSubmit();";
711 return sb.ToString ();
715 [MonoTODO ("optimize s.Replace")]
716 internal static string GetScriptLiteral (object ob)
720 else if (ob is string) {
721 string s = (string)ob;
722 s = s.Replace ("\\", "\\\\");
723 s = s.Replace ("\"", "\\\"");
724 return "\"" + s + "\"";
725 } else if (ob is bool) {
726 return ob.ToString().ToLower();
728 return ob.ToString ();
736 public string Script;
737 public ScriptEntry Next;
738 public bool Rendered;
740 public ScriptEntry (Type type, string key, string script)
750 internal static string EnsureEndsWithSemicolon (string value) {
751 if (value != null && value.Length > 0 && !value.EndsWith (";"))