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 List <int> eventValidationValues;
63 Hashtable expandoAttributes;
64 bool _hasRegisteredForEventValidationOnCallback;
67 internal ClientScriptManager (Page page)
73 public string GetPostBackClientEvent (Control control, string argument)
75 return GetPostBackEventReference (control, argument);
79 public string GetPostBackClientHyperlink (Control control, string argument)
81 return "javascript:" + GetPostBackEventReference (control, argument);
85 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
87 if (registerForEventValidation)
88 RegisterForEventValidation (control.UniqueID, argument);
89 return "javascript:" + GetPostBackEventReference (control, argument);
98 string GetPostBackEventReference (Control control, string argument)
101 throw new ArgumentNullException ("control");
103 page.RequiresPostBackScript ();
105 return String.Format ("{0}.__doPostBack('{1}','{2}')", page.theForm, control.UniqueID, argument);
107 return String.Format ("__doPostBack('{0}','{1}')", control.UniqueID, argument);
112 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
115 throw new ArgumentNullException ("control");
117 if (registerForEventValidation)
118 RegisterForEventValidation (control.UniqueID, argument);
119 return GetPostBackEventReference (control, argument);
122 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
125 throw new ArgumentNullException ("options");
126 if (registerForEventValidation)
127 RegisterForEventValidation (options);
128 return GetPostBackEventReference (options);
131 public string GetPostBackEventReference (PostBackOptions options)
134 throw new ArgumentNullException ("options");
136 if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus &&
137 !options.AutoPostBack && !options.PerformValidation)
139 if (!options.ClientSubmit)
142 if (options.RequiresJavaScriptProtocol)
143 return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
145 return GetPostBackEventReference (options.TargetControl, options.Argument);
148 RegisterWebFormClientScript ();
150 string actionUrl = options.ActionUrl;
151 if (actionUrl != null)
152 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
154 if(options.TrackFocus)
155 RegisterHiddenField (Page.LastFocusID, String.Empty);
157 string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
159 // Allow the page to transform ActionUrl to a portlet action url
160 if (actionUrl != null && page.PortletNamespace != null) {
161 actionUrl = page.CreateActionUrl(actionUrl);
166 return String.Format ("{0}WebForm_DoPostback({1},{2},{3},{4},{5},{6},{7},{8},{9})",
168 ClientScriptManager.GetScriptLiteral (options.TargetControl.UniqueID),
169 ClientScriptManager.GetScriptLiteral (options.Argument),
170 ClientScriptManager.GetScriptLiteral (actionUrl),
171 ClientScriptManager.GetScriptLiteral (options.AutoPostBack),
172 ClientScriptManager.GetScriptLiteral (options.PerformValidation),
173 ClientScriptManager.GetScriptLiteral (options.TrackFocus),
174 ClientScriptManager.GetScriptLiteral (options.ClientSubmit),
175 ClientScriptManager.GetScriptLiteral (options.ValidationGroup),
180 internal void RegisterWebFormClientScript ()
182 if (IsClientScriptIncludeRegistered (typeof (Page), "webform"))
185 RegisterClientScriptInclude (typeof (Page), "webform", GetWebResourceUrl (typeof (Page), "webform.js"));
186 page.RequiresPostBackScript ();
189 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
191 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
194 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
196 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
199 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
202 throw new ArgumentNullException ("control");
203 if(!(control is ICallbackEventHandler))
204 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
206 return GetCallbackEventReference (control.UniqueID, argument, clientCallback, context, clientErrorCallback, useAsync);
209 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
211 RegisterWebFormClientScript ();
213 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);
222 string GetWebResourceUrl(Type type, string resourceName)
225 throw new ArgumentNullException ("type");
227 if (resourceName == null || resourceName.Length == 0)
228 throw new ArgumentNullException ("type");
230 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
234 public bool IsClientScriptBlockRegistered (string key)
236 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
239 public bool IsClientScriptBlockRegistered (Type type, string key)
241 return IsScriptRegistered (clientScriptBlocks, type, key);
244 public bool IsStartupScriptRegistered (string key)
246 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
249 public bool IsStartupScriptRegistered (Type type, string key)
251 return IsScriptRegistered (startupScriptBlocks, type, key);
254 public bool IsOnSubmitStatementRegistered (string key)
256 return IsScriptRegistered (submitStatements, GetType(), key);
259 public bool IsOnSubmitStatementRegistered (Type type, string key)
261 return IsScriptRegistered (submitStatements, type, key);
264 public bool IsClientScriptIncludeRegistered (string key)
266 return IsScriptRegistered (scriptIncludes, GetType(), key);
269 public bool IsClientScriptIncludeRegistered (Type type, string key)
271 return IsScriptRegistered (scriptIncludes, type, key);
274 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
276 while (scriptList != null) {
277 if (scriptList.Type == type && scriptList.Key == key)
279 scriptList = scriptList.Next;
284 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
286 if (registeredArrayDeclares == null)
287 registeredArrayDeclares = new Hashtable();
289 if (!registeredArrayDeclares.ContainsKey (arrayName))
290 registeredArrayDeclares.Add (arrayName, new ArrayList());
292 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
295 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
297 ScriptEntry last = null;
298 ScriptEntry entry = scriptList;
300 while (entry != null) {
301 if (entry.Type == type && entry.Key == key)
308 script = "<script type=\"text/javascript\"" +
310 "language=\"javascript\"" +
312 ">\n<!--\n" + script + "\n// -->\n</script>";
315 entry = new ScriptEntry (type, key, script);
317 if (last != null) last.Next = entry;
318 else scriptList = entry;
321 internal void RegisterClientScriptBlock (string key, string script)
323 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
326 public void RegisterClientScriptBlock (Type type, string key, string script)
328 RegisterClientScriptBlock (type, key, script, false);
331 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
334 throw new ArgumentNullException ("type");
336 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
339 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
341 if (hiddenFields == null)
342 hiddenFields = new Hashtable ();
344 if (!hiddenFields.ContainsKey (hiddenFieldName))
345 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
348 internal void RegisterOnSubmitStatement (string key, string script)
350 RegisterScript (ref submitStatements, GetType (), key, script, false);
353 public void RegisterOnSubmitStatement (Type type, string key, string script)
356 throw new ArgumentNullException ("type");
358 RegisterScript (ref submitStatements, type, key, script, false);
361 internal void RegisterStartupScript (string key, string script)
363 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
366 public void RegisterStartupScript (Type type, string key, string script)
368 RegisterStartupScript (type, key, script, false);
371 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
374 throw new ArgumentNullException ("type");
376 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
379 public void RegisterClientScriptInclude (string key, string url)
381 RegisterClientScriptInclude (GetType (), key, url);
384 public void RegisterClientScriptInclude (Type type, string key, string url)
387 throw new ArgumentNullException ("type");
388 if (url == null || url.Length == 0)
389 throw new ArgumentException ("url");
391 RegisterScript (ref scriptIncludes, type, key, url, false);
395 public void RegisterClientScriptResource (Type type, string resourceName)
397 RegisterScript (ref scriptIncludes, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), false);
400 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
402 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
405 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
407 if (controlId == null)
408 throw new ArgumentNullException ("controlId");
410 if (attributeName == null)
411 throw new ArgumentNullException ("attributeName");
413 if (expandoAttributes == null)
414 expandoAttributes = new Hashtable ();
416 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
418 list = new ListDictionary ();
419 expandoAttributes [controlId] = list;
422 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
425 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
426 private int CalculateEventHash (string uniqueId, string argument)
428 int uniqueIdHash = uniqueId.GetHashCode ();
429 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
430 return (uniqueIdHash ^ argumentHash);
433 public void RegisterForEventValidation (PostBackOptions options)
435 // MS.NET does not check for options == null, so we won't too...
436 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
439 public void RegisterForEventValidation (string uniqueId)
441 RegisterForEventValidation (uniqueId, null);
444 public void RegisterForEventValidation (string uniqueId, string argument)
446 if (!page.EnableEventValidation)
448 if (uniqueId == null || uniqueId.Length == 0)
451 _hasRegisteredForEventValidationOnCallback = true;
452 else if (page.LifeCycle < PageLifeCycle.Render)
453 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
454 if (eventValidationValues == null)
455 eventValidationValues = new List <int> ();
458 int hash = CalculateEventHash (uniqueId, argument);
459 if (eventValidationValues.BinarySearch (hash) < 0)
460 eventValidationValues.Add (hash);
463 public void ValidateEvent (string uniqueId)
465 ValidateEvent (uniqueId, null);
468 public void ValidateEvent (string uniqueId, string argument)
470 if (uniqueId == null || uniqueId.Length == 0)
471 throw new ArgumentException ("must not be null or empty", "uniqueId");
472 if (!page.EnableEventValidation)
474 if (eventValidationValues == null)
477 int hash = CalculateEventHash (uniqueId, argument);
478 if (eventValidationValues.BinarySearch (hash) < 0)
483 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.");
486 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
488 while (scriptList != null) {
489 writer.WriteLine (scriptList.Script);
490 scriptList = scriptList.Next;
495 internal void RestoreEventValidationState (string fieldValue)
497 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
499 IStateFormatter fmt = page.GetFormatter ();
500 int [] eventValues = (int []) fmt.Deserialize (fieldValue);
501 #if TARGET_JVM // FIXME: No support yet for passing 'int[]' as 'T[]'
502 eventValidationValues = new List<int> (eventValues.Length);
503 for (int i = 0; i < eventValues.Length; i++)
504 eventValidationValues.Add(eventValues[i]);
506 eventValidationValues = new List<int> (eventValues);
508 eventValidationValues.Sort ();
511 internal void SaveEventValidationState ()
513 if (!page.EnableEventValidation)
516 string eventValidation = GetEventValidationStateFormatted ();
517 if (eventValidation == null)
520 RegisterHiddenField (EventStateFieldName, eventValidation);
523 internal string GetEventValidationStateFormatted ()
525 if (eventValidationValues == null || eventValidationValues.Count == 0)
528 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
531 IStateFormatter fmt = page.GetFormatter ();
532 int [] array = new int [eventValidationValues.Count];
533 #if TARGET_JVM // FIXME: No support yet for passing 'int[]' as 'T[]'
534 ((ICollection)eventValidationValues).CopyTo (array, 0);
536 eventValidationValues.CopyTo (array);
538 return fmt.Serialize (array);
541 internal string EventStateFieldName
543 get { return "__EVENTVALIDATION"; }
546 internal void WriteExpandoAttributes (HtmlTextWriter writer)
548 if (expandoAttributes == null)
552 WriteBeginScriptBlock (writer);
554 foreach (string controlId in expandoAttributes.Keys) {
555 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
556 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
557 foreach (string attributeName in attrs.Keys) {
558 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
561 WriteEndScriptBlock (writer);
566 internal void WriteBeginScriptBlock (HtmlTextWriter writer)
568 writer.WriteLine ("<script"+
570 " language=\"javascript\""+
572 " type=\"text/javascript\">");
573 writer.WriteLine ("<!--");
576 internal void WriteEndScriptBlock (HtmlTextWriter writer)
578 writer.WriteLine ("// -->");
579 writer.WriteLine ("</script>");
582 internal void WriteHiddenFields (HtmlTextWriter writer)
584 if (hiddenFields == null)
587 writer.RenderBeginTag (HtmlTextWriterTag.Div);
588 foreach (string key in hiddenFields.Keys) {
589 string value = hiddenFields [key] as string;
590 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, value);
592 writer.RenderEndTag (); // DIV
596 internal void WriteClientScriptIncludes (HtmlTextWriter writer)
598 ScriptEntry entry = scriptIncludes;
599 while (entry != null) {
600 if (!entry.Rendered) {
602 if (!page.IsPortletRender)
604 writer.WriteLine ("\n<script src=\"{0}\" type=\"text/javascript\"></script>", entry.Script);
607 string scriptKey = "inc_" + entry.Key.GetHashCode ().ToString ("X");
608 writer.WriteLine ("\n<script type=\"text/javascript\">");
609 writer.WriteLine ("<!--");
610 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
611 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
612 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", entry.Script);
613 writer.WriteLine ("// -->");
614 writer.WriteLine ("</script>");
617 entry.Rendered = true;
623 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
625 WriteScripts (writer, clientScriptBlocks);
628 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
630 WriteScripts (writer, startupScriptBlocks);
633 internal void WriteArrayDeclares (HtmlTextWriter writer)
635 if (registeredArrayDeclares != null) {
637 WriteBeginScriptBlock (writer);
638 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
639 while (arrayEnum.MoveNext()) {
640 writer.Write("\tvar ");
641 writer.Write(arrayEnum.Key);
642 writer.Write(" = new Array(");
643 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
645 while (arrayListEnum.MoveNext()) {
650 writer.Write(arrayListEnum.Current);
652 writer.WriteLine(");");
654 // in addition, add a form array declaration
655 if (page.IsPortletRender) {
656 writer.Write ("\t" + page.theForm + ".");
657 writer.Write (arrayEnum.Key);
658 writer.Write (" = ");
659 writer.Write (arrayEnum.Key);
660 writer.WriteLine (";");
664 WriteEndScriptBlock (writer);
670 internal string GetClientValidationEvent (string validationGroup) {
671 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
673 if (page.IsPortletRender)
674 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
680 internal string GetClientValidationEvent ()
682 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
684 if (page.IsPortletRender)
685 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
691 internal string WriteSubmitStatements ()
693 if (submitStatements == null) return null;
695 StringBuilder sb = new StringBuilder ();
696 ScriptEntry entry = submitStatements;
697 while (entry != null) {
699 sb.Append (EnsureEndsWithSemicolon (entry.Script));
701 sb.Append (entry.Script);
706 RegisterClientScriptBlock ("HtmlForm-OnSubmitStatemen",
707 @"<script type=""text/javascript"">
709 " + page.theForm + @".WebForm_OnSubmit = function () {
710 " + sb.ToString () + @"
715 return "javascript:return this.WebForm_OnSubmit();";
718 return sb.ToString ();
722 [MonoTODO ("optimize s.Replace")]
723 internal static string GetScriptLiteral (object ob)
727 else if (ob is string) {
728 string s = (string)ob;
729 s = s.Replace ("\\", "\\\\");
730 s = s.Replace ("\"", "\\\"");
731 return "\"" + s + "\"";
732 } else if (ob is bool) {
733 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
735 return ob.ToString ();
743 public string Script;
744 public ScriptEntry Next;
745 public bool Rendered;
747 public ScriptEntry (Type type, string key, string script)
757 internal static string EnsureEndsWithSemicolon (string value) {
758 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')