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;
61 int [] eventValidationValues;
62 int eventValidationPos = 0;
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 (_webFormClientScriptRequired)
185 page.RequiresPostBackScript ();
186 _webFormClientScriptRequired = true;
189 bool _webFormClientScriptRendered;
190 bool _webFormClientScriptRequired;
192 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
193 if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
195 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"));
196 _webFormClientScriptRendered = true;
200 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
202 return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
205 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
207 return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
210 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
213 throw new ArgumentNullException ("control");
214 if(!(control is ICallbackEventHandler))
215 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
217 return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
220 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
222 RegisterWebFormClientScript ();
224 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);
233 string GetWebResourceUrl(Type type, string resourceName)
236 throw new ArgumentNullException ("type");
238 if (resourceName == null || resourceName.Length == 0)
239 throw new ArgumentNullException ("type");
241 return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName);
245 public bool IsClientScriptBlockRegistered (string key)
247 return IsScriptRegistered (clientScriptBlocks, GetType(), key);
250 public bool IsClientScriptBlockRegistered (Type type, string key)
252 return IsScriptRegistered (clientScriptBlocks, type, key);
255 public bool IsStartupScriptRegistered (string key)
257 return IsScriptRegistered (startupScriptBlocks, GetType(), key);
260 public bool IsStartupScriptRegistered (Type type, string key)
262 return IsScriptRegistered (startupScriptBlocks, type, key);
265 public bool IsOnSubmitStatementRegistered (string key)
267 return IsScriptRegistered (submitStatements, GetType(), key);
270 public bool IsOnSubmitStatementRegistered (Type type, string key)
272 return IsScriptRegistered (submitStatements, type, key);
275 public bool IsClientScriptIncludeRegistered (string key)
277 return IsClientScriptIncludeRegistered (GetType (), key);
280 public bool IsClientScriptIncludeRegistered (Type type, string key)
282 return IsScriptRegistered (clientScriptBlocks, type, "include-" + key);
285 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
287 while (scriptList != null) {
288 if (scriptList.Type == type && scriptList.Key == key)
290 scriptList = scriptList.Next;
295 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
297 if (registeredArrayDeclares == null)
298 registeredArrayDeclares = new Hashtable();
300 if (!registeredArrayDeclares.ContainsKey (arrayName))
301 registeredArrayDeclares.Add (arrayName, new ArrayList());
303 ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
306 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
308 RegisterScript (ref scriptList, type, key, script, addScriptTags ? ScriptEntryFormat.AddScriptTag : ScriptEntryFormat.None);
311 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, ScriptEntryFormat format)
313 ScriptEntry last = null;
314 ScriptEntry entry = scriptList;
316 while (entry != null) {
317 if (entry.Type == type && entry.Key == key)
323 entry = new ScriptEntry (type, key, script, format);
325 if (last != null) last.Next = entry;
326 else scriptList = entry;
329 internal void RegisterClientScriptBlock (string key, string script)
331 RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
334 public void RegisterClientScriptBlock (Type type, string key, string script)
336 RegisterClientScriptBlock (type, key, script, false);
339 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
342 throw new ArgumentNullException ("type");
344 RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
347 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
349 if (hiddenFields == null)
350 hiddenFields = new Hashtable ();
352 if (!hiddenFields.ContainsKey (hiddenFieldName))
353 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
356 internal void RegisterOnSubmitStatement (string key, string script)
358 RegisterScript (ref submitStatements, GetType (), key, script, false);
361 public void RegisterOnSubmitStatement (Type type, string key, string script)
364 throw new ArgumentNullException ("type");
366 RegisterScript (ref submitStatements, type, key, script, false);
369 internal void RegisterStartupScript (string key, string script)
371 RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
374 public void RegisterStartupScript (Type type, string key, string script)
376 RegisterStartupScript (type, key, script, false);
379 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
382 throw new ArgumentNullException ("type");
384 RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
387 public void RegisterClientScriptInclude (string key, string url)
389 RegisterClientScriptInclude (GetType (), key, url);
392 public void RegisterClientScriptInclude (Type type, string key, string url)
395 throw new ArgumentNullException ("type");
396 if (url == null || url.Length == 0)
397 throw new ArgumentException ("url");
399 RegisterScript (ref clientScriptBlocks, type, "include-" + key, url, ScriptEntryFormat.Include);
403 public void RegisterClientScriptResource (Type type, string resourceName)
405 RegisterScript (ref clientScriptBlocks, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), ScriptEntryFormat.Include);
408 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
410 RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
413 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
415 if (controlId == null)
416 throw new ArgumentNullException ("controlId");
418 if (attributeName == null)
419 throw new ArgumentNullException ("attributeName");
421 if (expandoAttributes == null)
422 expandoAttributes = new Hashtable ();
424 ListDictionary list = (ListDictionary)expandoAttributes [controlId];
426 list = new ListDictionary ();
427 expandoAttributes [controlId] = list;
430 list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
433 private void EnsureEventValidationArray ()
435 if (eventValidationValues == null)
436 eventValidationValues = new int [64];
438 int len = eventValidationValues.Length;
440 if (eventValidationPos >= len) {
441 int [] tmp = new int [len * 2];
442 Array.Copy (eventValidationValues, tmp, len);
443 eventValidationValues = tmp;
447 internal void ResetEventValidationState ()
449 eventValidationPos = 0;
452 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
453 private int CalculateEventHash (string uniqueId, string argument)
455 int uniqueIdHash = uniqueId.GetHashCode ();
456 int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
457 return (uniqueIdHash ^ argumentHash);
460 public void RegisterForEventValidation (PostBackOptions options)
462 // MS.NET does not check for options == null, so we won't too...
463 RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
466 public void RegisterForEventValidation (string uniqueId)
468 RegisterForEventValidation (uniqueId, null);
471 public void RegisterForEventValidation (string uniqueId, string argument)
473 if (!page.EnableEventValidation)
475 if (uniqueId == null || uniqueId.Length == 0)
478 _hasRegisteredForEventValidationOnCallback = true;
479 else if (page.LifeCycle < PageLifeCycle.Render)
480 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
482 EnsureEventValidationArray ();
484 int hash = CalculateEventHash (uniqueId, argument);
485 for (int i = 0; i < eventValidationPos; i++)
486 if (eventValidationValues [i] == hash)
488 eventValidationValues [eventValidationPos++] = hash;
491 public void ValidateEvent (string uniqueId)
493 ValidateEvent (uniqueId, null);
496 public void ValidateEvent (string uniqueId, string argument)
498 if (uniqueId == null || uniqueId.Length == 0)
499 throw new ArgumentException ("must not be null or empty", "uniqueId");
500 if (!page.EnableEventValidation)
502 if (eventValidationValues == null)
505 int hash = CalculateEventHash (uniqueId, argument);
506 for (int i = 0; i < eventValidationValues.Length; i++)
507 if (eventValidationValues [i] == hash)
511 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.");
514 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
516 if (scriptList == null)
521 while (scriptList != null) {
522 switch (scriptList.Format) {
523 case ScriptEntryFormat.AddScriptTag:
524 EnsureBeginScriptBlock (writer);
525 writer.Write (scriptList.Script);
527 case ScriptEntryFormat.Include:
528 EnsureEndScriptBlock (writer);
529 WriteClientScriptInclude (writer, scriptList.Script);
532 EnsureEndScriptBlock (writer);
533 writer.WriteLine (scriptList.Script);
536 scriptList = scriptList.Next;
538 EnsureEndScriptBlock (writer);
541 bool _scriptTagOpened;
543 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
544 if (!_scriptTagOpened) {
545 WriteBeginScriptBlock (writer);
546 _scriptTagOpened = true;
550 void EnsureEndScriptBlock (HtmlTextWriter writer) {
551 if (_scriptTagOpened) {
552 WriteEndScriptBlock (writer);
553 _scriptTagOpened = false;
558 internal void RestoreEventValidationState (string fieldValue)
560 if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
562 IStateFormatter fmt = page.GetFormatter ();
563 eventValidationValues = (int []) fmt.Deserialize (fieldValue);
564 eventValidationPos = eventValidationValues.Length;
567 internal void SaveEventValidationState ()
569 if (!page.EnableEventValidation)
572 string eventValidation = GetEventValidationStateFormatted ();
573 if (eventValidation == null)
576 RegisterHiddenField (EventStateFieldName, eventValidation);
579 internal string GetEventValidationStateFormatted ()
581 if (eventValidationValues == null || eventValidationValues.Length == 0)
584 if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
587 IStateFormatter fmt = page.GetFormatter ();
588 int [] array = new int [eventValidationPos];
589 Array.Copy (eventValidationValues, array, eventValidationPos);
590 return fmt.Serialize (array);
593 internal string EventStateFieldName
595 get { return "__EVENTVALIDATION"; }
598 internal void WriteExpandoAttributes (HtmlTextWriter writer)
600 if (expandoAttributes == null)
604 WriteBeginScriptBlock (writer);
606 foreach (string controlId in expandoAttributes.Keys) {
607 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
608 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
609 foreach (string attributeName in attrs.Keys) {
610 writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
613 WriteEndScriptBlock (writer);
618 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
620 writer.WriteLine ("<script"+
622 " language=\"javascript\""+
624 " type=\"text/javascript\">");
625 writer.WriteLine ("<!--");
628 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
630 writer.WriteLine ("// -->");
631 writer.WriteLine ("</script>");
634 internal void WriteHiddenFields (HtmlTextWriter writer)
636 if (hiddenFields == null)
639 writer.RenderBeginTag (HtmlTextWriterTag.Div);
640 foreach (string key in hiddenFields.Keys) {
641 string value = hiddenFields [key] as string;
642 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, value);
644 writer.RenderEndTag (); // DIV
648 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path) {
650 if (!page.IsPortletRender)
652 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
655 string scriptKey = "inc_" + path.GetHashCode ().ToString ("X");
656 writer.WriteLine ("<script type=\"text/javascript\">");
657 writer.WriteLine ("<!--");
658 writer.WriteLine ("if (document.{0} == null) {{", scriptKey);
659 writer.WriteLine ("\tdocument.{0} = true", scriptKey);
660 writer.WriteLine ("\tdocument.write('<script src=\"{0}\" type=\"text/javascript\"><\\/script>'); }}", path);
661 writer.WriteLine ("// -->");
662 writer.WriteLine ("</script>");
667 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
669 WriteScripts (writer, clientScriptBlocks);
672 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
674 WriteScripts (writer, startupScriptBlocks);
677 internal void WriteArrayDeclares (HtmlTextWriter writer)
679 if (registeredArrayDeclares != null) {
681 WriteBeginScriptBlock (writer);
682 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
683 while (arrayEnum.MoveNext()) {
684 writer.Write("\tvar ");
685 writer.Write(arrayEnum.Key);
686 writer.Write(" = new Array(");
687 IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
689 while (arrayListEnum.MoveNext()) {
694 writer.Write(arrayListEnum.Current);
696 writer.WriteLine(");");
698 // in addition, add a form array declaration
699 if (page.IsPortletRender) {
700 writer.Write ("\t" + page.theForm + ".");
701 writer.Write (arrayEnum.Key);
702 writer.Write (" = ");
703 writer.Write (arrayEnum.Key);
704 writer.WriteLine (";");
708 WriteEndScriptBlock (writer);
714 internal string GetClientValidationEvent (string validationGroup) {
715 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
717 if (page.IsPortletRender)
718 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
724 internal string GetClientValidationEvent ()
726 string eventScript = "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
728 if (page.IsPortletRender)
729 return "if (typeof(SetValidatorContext) == 'function') SetValidatorContext ('" + page.theForm + "'); " + eventScript;
735 internal string WriteSubmitStatements ()
737 if (submitStatements == null) return null;
739 StringBuilder sb = new StringBuilder ();
740 ScriptEntry entry = submitStatements;
741 while (entry != null) {
743 sb.Append (EnsureEndsWithSemicolon (entry.Script));
745 sb.Append (entry.Script);
750 RegisterClientScriptBlock ("HtmlForm-OnSubmitStatemen",
751 @"<script type=""text/javascript"">
753 " + page.theForm + @".WebForm_OnSubmit = function () {
754 " + sb.ToString () + @"
759 return "javascript:return this.WebForm_OnSubmit();";
762 return sb.ToString ();
766 [MonoTODO ("optimize s.Replace")]
767 internal static string GetScriptLiteral (object ob)
771 else if (ob is string) {
772 string s = (string)ob;
773 s = s.Replace ("\\", "\\\\");
774 s = s.Replace ("\"", "\\\"");
775 return "\"" + s + "\"";
776 } else if (ob is bool) {
777 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
779 return ob.ToString ();
783 sealed class ScriptEntry
785 public readonly Type Type;
786 public readonly string Key;
787 public readonly string Script;
788 public readonly ScriptEntryFormat Format;
789 public ScriptEntry Next;
791 public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
799 enum ScriptEntryFormat
808 internal static string EnsureEndsWithSemicolon (string value) {
809 if (value != null && value.Length > 0 && value [value.Length - 1] != ';')