A slightly more elegant way of dealing with 'item==null' issue
[mono.git] / mcs / class / System.Web / System.Web.UI / ClientScriptManager.cs
1 //
2 // System.Web.UI.ClientScriptManager.cs
3 //
4 // Authors:
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)
9 //
10 // (C) 2002,2003 Ximian, Inc. (http://www.ximian.com)
11 // (c) 2003 Novell, Inc. (http://www.novell.com)
12 //
13
14 //
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:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
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.
33 //
34
35 using System;
36 using System.Collections;
37 #if NET_2_0
38 using System.Collections.Generic;
39 #endif
40 using System.Text;
41 using System.Collections.Specialized;
42 using System.Web.Util;
43 using System.Globalization;
44
45 namespace System.Web.UI
46 {
47         #if NET_2_0
48         public sealed
49         #else
50         internal
51         #endif
52                 class ClientScriptManager
53         {
54                 Hashtable registeredArrayDeclares;
55                 ScriptEntry clientScriptBlocks;
56                 ScriptEntry startupScriptBlocks;
57                 internal Hashtable hiddenFields;
58                 ScriptEntry submitStatements;
59                 Page page;
60 #if NET_2_0
61                 int [] eventValidationValues;
62                 int eventValidationPos = 0;
63                 Hashtable expandoAttributes;
64                 bool _hasRegisteredForEventValidationOnCallback;
65 #endif
66                 
67                 internal ClientScriptManager (Page page)
68                 {
69                         this.page = page;
70                 }
71
72 #if !NET_2_0
73                 public string GetPostBackClientEvent (Control control, string argument)
74                 {
75                         return GetPostBackEventReference (control, argument);
76                 }
77 #endif
78
79                 public string GetPostBackClientHyperlink (Control control, string argument)
80                 {
81                         return "javascript:" + GetPostBackEventReference (control, argument);
82                 }
83         
84 #if NET_2_0
85                 public string GetPostBackClientHyperlink (Control control, string argument, bool registerForEventValidation)
86                 {
87                         if (registerForEventValidation)
88                                 RegisterForEventValidation (control.UniqueID, argument);
89                         return "javascript:" + GetPostBackEventReference (control, argument);
90                 }
91 #endif          
92
93 #if !NET_2_0
94                 internal
95 #else
96                 public
97 #endif
98                 string GetPostBackEventReference (Control control, string argument)
99                 {
100                         if (control == null)
101                                 throw new ArgumentNullException ("control");
102                         
103                         page.RequiresPostBackScript ();
104                 if(page.IsMultiForm)
105                         return String.Format ("{0}.__doPostBack('{1}','{2}')", page.theForm, control.UniqueID, argument);
106                 else
107                         return String.Format ("__doPostBack('{0}','{1}')", control.UniqueID, argument);
108                 }
109
110 #if NET_2_0
111                 public string GetPostBackEventReference (Control control, string argument, bool registerForEventValidation)
112                 {
113                         if (control == null)
114                                 throw new ArgumentNullException ("control");
115                         
116                         if (registerForEventValidation)
117                                 RegisterForEventValidation (control.UniqueID, argument);
118                         return GetPostBackEventReference (control, argument);
119                 }
120                 
121                 public string GetPostBackEventReference (PostBackOptions options, bool registerForEventValidation)
122                 {
123                         if (options == null)
124                                 throw new ArgumentNullException ("options");
125                         if (registerForEventValidation)
126                                 RegisterForEventValidation (options);
127                         return GetPostBackEventReference (options);
128                 }
129                 
130                 public string GetPostBackEventReference (PostBackOptions options)
131                 {
132                         if (options == null)
133                                 throw new ArgumentNullException ("options");
134
135                         if (options.ActionUrl == null && options.ValidationGroup == null && !options.TrackFocus && 
136                                 !options.AutoPostBack && !options.PerformValidation)
137                         {
138                                 if (!options.ClientSubmit)
139                                         return null;
140
141                                 if (options.RequiresJavaScriptProtocol)
142                                         return GetPostBackClientHyperlink (options.TargetControl, options.Argument);
143                                 else
144                                         return GetPostBackEventReference (options.TargetControl, options.Argument);
145                         }
146
147                         RegisterWebFormClientScript ();
148
149                         string actionUrl = options.ActionUrl;
150                         if (actionUrl != null)
151                                 RegisterHiddenField (Page.PreviousPageID, page.Request.FilePath);
152
153                         if(options.TrackFocus)
154                                 RegisterHiddenField (Page.LastFocusID, String.Empty);
155
156                         string prefix = options.RequiresJavaScriptProtocol ? "javascript:" : "";
157                         if (page.IsMultiForm)
158                                 prefix += page.theForm + ".";
159 #if TARGET_J2EE
160                         // Allow the page to transform ActionUrl to a portlet action url
161                         if (actionUrl != null && page.PortletNamespace != null) {
162                                 actionUrl = page.CreateActionUrl(actionUrl);
163                                 prefix += "Portal";
164                         }
165 #endif
166
167                         return String.Format ("{0}WebForm_DoPostback({1},{2},{3},{4},{5},{6},{7},{8})", 
168                                         prefix,
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)
177                                 );
178                 }
179
180                 internal void RegisterWebFormClientScript ()
181                 {
182                         if (_webFormClientScriptRequired)
183                                 return;
184
185                         page.RequiresPostBackScript ();
186                         _webFormClientScriptRequired = true;
187                 }
188
189                 bool _webFormClientScriptRendered;
190                 bool _webFormClientScriptRequired;
191
192                 internal void WriteWebFormClientScript (HtmlTextWriter writer) {
193                         if (!_webFormClientScriptRendered && _webFormClientScriptRequired) {
194                                 writer.WriteLine ();
195                                 WriteClientScriptInclude (writer, GetWebResourceUrl (typeof (Page), "webform.js"));
196                                 WriteBeginScriptBlock (writer);
197                                 writer.WriteLine ("WebForm_Initialize({0});", page.IsMultiForm ? page.theForm : "window");
198                                 WriteEndScriptBlock (writer);
199                                 _webFormClientScriptRendered = true;
200                         }
201                 }
202                 
203                 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context)
204                 {
205                         return GetCallbackEventReference (control, argument, clientCallback, context, null, false);
206                 }
207
208                 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, bool useAsync)
209                 {
210                         return GetCallbackEventReference (control, argument, clientCallback, context, null, useAsync);
211                 }
212
213                 public string GetCallbackEventReference (Control control, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
214                 {
215                         if (control == null)
216                                 throw new ArgumentNullException ("control");
217                         if(!(control is ICallbackEventHandler))
218                                 throw new InvalidOperationException ("The control must implement the ICallbackEventHandler interface and provide a RaiseCallbackEvent method.");
219
220                         return GetCallbackEventReference ("'" + control.UniqueID + "'", argument, clientCallback, context, clientErrorCallback, useAsync);
221                 }
222
223                 public string GetCallbackEventReference (string target, string argument, string clientCallback, string context, string clientErrorCallback, bool useAsync)
224                 {
225                         RegisterWebFormClientScript ();
226                         
227                         return string.Format ("{6}WebForm_DoCallback({0},{1},{2},{3},{4},{5})", target, ((argument == null) ? "null" : argument), clientCallback, ((context == null) ? "null" : context), ((clientErrorCallback == null) ? "null" : clientErrorCallback), (useAsync ? "true" : "false"), (page.IsMultiForm ? page.theForm + "." : null));
228                 }
229 #endif
230                 
231 #if NET_2_0
232                 public
233 #else
234                 internal
235 #endif
236                 string GetWebResourceUrl(Type type, string resourceName)
237                 {
238                         if (type == null)
239                                 throw new ArgumentNullException ("type");
240                 
241                         if (resourceName == null || resourceName.Length == 0)
242                                 throw new ArgumentNullException ("type");
243                 
244                         return System.Web.Handlers.AssemblyResourceLoader.GetResourceUrl (type, resourceName); 
245                 }
246                 
247
248                 public bool IsClientScriptBlockRegistered (string key)
249                 {
250                         return IsScriptRegistered (clientScriptBlocks, GetType(), key);
251                 }
252         
253                 public bool IsClientScriptBlockRegistered (Type type, string key)
254                 {
255                         return IsScriptRegistered (clientScriptBlocks, type, key);
256                 }
257         
258                 public bool IsStartupScriptRegistered (string key)
259                 {
260                         return IsScriptRegistered (startupScriptBlocks, GetType(), key);
261                 }
262         
263                 public bool IsStartupScriptRegistered (Type type, string key)
264                 {
265                         return IsScriptRegistered (startupScriptBlocks, type, key);
266                 }
267                 
268                 public bool IsOnSubmitStatementRegistered (string key)
269                 {
270                         return IsScriptRegistered (submitStatements, GetType(), key);
271                 }
272         
273                 public bool IsOnSubmitStatementRegistered (Type type, string key)
274                 {
275                         return IsScriptRegistered (submitStatements, type, key);
276                 }
277                 
278                 public bool IsClientScriptIncludeRegistered (string key)
279                 {
280                         return IsClientScriptIncludeRegistered (GetType (), key);
281                 }
282         
283                 public bool IsClientScriptIncludeRegistered (Type type, string key)
284                 {
285                         return IsScriptRegistered (clientScriptBlocks, type, "include-" + key);
286                 }
287                 
288                 bool IsScriptRegistered (ScriptEntry scriptList, Type type, string key)
289                 {
290                         while (scriptList != null) {
291                                 if (scriptList.Type == type && scriptList.Key == key)
292                                         return true;
293                                 scriptList = scriptList.Next;
294                         }
295                         return false;
296                 }
297                 
298                 public void RegisterArrayDeclaration (string arrayName, string arrayValue)
299                 {
300                         if (registeredArrayDeclares == null)
301                                 registeredArrayDeclares = new Hashtable();
302         
303                         if (!registeredArrayDeclares.ContainsKey (arrayName))
304                                 registeredArrayDeclares.Add (arrayName, new ArrayList());
305         
306                         ((ArrayList) registeredArrayDeclares[arrayName]).Add(arrayValue);
307                 }
308
309                 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, bool addScriptTags)
310                 {
311                         RegisterScript (ref scriptList, type, key, script, addScriptTags ? ScriptEntryFormat.AddScriptTag : ScriptEntryFormat.None);
312                 }
313
314                 void RegisterScript (ref ScriptEntry scriptList, Type type, string key, string script, ScriptEntryFormat format)
315                 {
316                         ScriptEntry last = null;
317                         ScriptEntry entry = scriptList;
318
319                         while (entry != null) {
320                                 if (entry.Type == type && entry.Key == key)
321                                         return;
322                                 last = entry;
323                                 entry = entry.Next;
324                         }
325
326                         entry = new ScriptEntry (type, key, script, format);
327                         
328                         if (last != null) last.Next = entry;
329                         else scriptList = entry;
330                 }
331         
332                 internal void RegisterClientScriptBlock (string key, string script)
333                 {
334                         RegisterScript (ref clientScriptBlocks, GetType(), key, script, false);
335                 }
336         
337                 public void RegisterClientScriptBlock (Type type, string key, string script)
338                 {
339                         RegisterClientScriptBlock (type, key, script, false);
340                 }
341         
342                 public void RegisterClientScriptBlock (Type type, string key, string script, bool addScriptTags)
343                 {
344                         if (type == null)
345                                 throw new ArgumentNullException ("type");
346
347                         RegisterScript (ref clientScriptBlocks, type, key, script, addScriptTags);
348                 }
349         
350                 public void RegisterHiddenField (string hiddenFieldName, string hiddenFieldInitialValue)
351                 {
352                         if (hiddenFields == null)
353                                 hiddenFields = new Hashtable ();
354
355                         if (!hiddenFields.ContainsKey (hiddenFieldName))
356                                 hiddenFields.Add (hiddenFieldName, hiddenFieldInitialValue);
357                 }
358         
359                 internal void RegisterOnSubmitStatement (string key, string script)
360                 {
361                         RegisterScript (ref submitStatements, GetType (), key, script, false);
362                 }
363         
364                 public void RegisterOnSubmitStatement (Type type, string key, string script)
365                 {
366                         if (type == null)
367                                 throw new ArgumentNullException ("type");
368                         
369                         RegisterScript (ref submitStatements, type, key, script, false);
370                 }
371         
372                 internal void RegisterStartupScript (string key, string script)
373                 {
374                         RegisterScript (ref startupScriptBlocks, GetType(), key, script, false);
375                 }
376                 
377                 public void RegisterStartupScript (Type type, string key, string script)
378                 {
379                         RegisterStartupScript (type, key, script, false);
380                 }
381                 
382                 public void RegisterStartupScript (Type type, string key, string script, bool addScriptTags)
383                 {
384                         if (type == null)
385                                 throw new ArgumentNullException ("type");
386
387                         RegisterScript (ref startupScriptBlocks, type, key, script, addScriptTags);
388                 }
389
390                 public void RegisterClientScriptInclude (string key, string url)
391                 {
392                         RegisterClientScriptInclude (GetType (), key, url);
393                 }
394                 
395                 public void RegisterClientScriptInclude (Type type, string key, string url)
396                 {
397                         if (type == null)
398                                 throw new ArgumentNullException ("type");
399                         if (url == null || url.Length == 0)
400                                 throw new ArgumentException ("url");
401
402                         RegisterScript (ref clientScriptBlocks, type, "include-" + key, url, ScriptEntryFormat.Include);
403                 }
404
405 #if NET_2_0
406                 public void RegisterClientScriptResource (Type type, string resourceName)
407                 {
408                         RegisterScript (ref clientScriptBlocks, type, "resource-" + resourceName, GetWebResourceUrl (type, resourceName), ScriptEntryFormat.Include);
409                 }
410
411                 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue)
412                 {
413                         RegisterExpandoAttribute (controlId, attributeName, attributeValue, true);
414                 }
415
416                 public void RegisterExpandoAttribute (string controlId, string attributeName, string attributeValue, bool encode)
417                 {
418                         if (controlId == null)
419                                 throw new ArgumentNullException ("controlId");
420
421                         if (attributeName == null)
422                                 throw new ArgumentNullException ("attributeName");
423                         
424                         if (expandoAttributes == null)
425                                 expandoAttributes = new Hashtable ();
426
427                         ListDictionary list = (ListDictionary)expandoAttributes [controlId];
428                         if (list == null) {
429                                 list = new ListDictionary ();
430                                 expandoAttributes [controlId] = list;
431                         }
432
433                         list.Add (attributeName, encode ? StrUtils.EscapeQuotesAndBackslashes (attributeValue) : attributeValue);
434                 }
435
436                 private void EnsureEventValidationArray ()
437                 {
438                         if (eventValidationValues == null)
439                                 eventValidationValues = new int [64];
440
441                         int len = eventValidationValues.Length;
442
443                         if (eventValidationPos >= len) {
444                                 int [] tmp = new int [len * 2];
445                                 Array.Copy (eventValidationValues, tmp, len);
446                                 eventValidationValues = tmp;
447                         }
448                 }
449
450                 internal void ResetEventValidationState ()
451                 {
452                         eventValidationPos = 0;
453                 }
454
455                 // Implemented following the description in http://odetocode.com/Blogs/scott/archive/2006/03/20/3145.aspx
456                 private int CalculateEventHash (string uniqueId, string argument)
457                 {
458                         int uniqueIdHash = uniqueId.GetHashCode ();
459                         int argumentHash = String.IsNullOrEmpty (argument) ? 0 : argument.GetHashCode ();
460                         return (uniqueIdHash ^ argumentHash);
461                 }
462                 
463                 public void RegisterForEventValidation (PostBackOptions options)
464                 {
465                         // MS.NET does not check for options == null, so we won't too...
466                         RegisterForEventValidation (options.TargetControl.UniqueID, options.Argument);
467                 }
468                 
469                 public void RegisterForEventValidation (string uniqueId)
470                 {
471                         RegisterForEventValidation (uniqueId, null);
472                 }
473                 
474                 public void RegisterForEventValidation (string uniqueId, string argument)
475                 {
476                         if (!page.EnableEventValidation)
477                                 return;
478                         if (uniqueId == null || uniqueId.Length == 0)
479                                 return;
480                         if (page.IsCallback)
481                                 _hasRegisteredForEventValidationOnCallback = true;
482                         else if (page.LifeCycle < PageLifeCycle.Render)
483                                 throw new InvalidOperationException ("RegisterForEventValidation may only be called from the Render method");
484
485                         EnsureEventValidationArray ();
486                         
487                         int hash = CalculateEventHash (uniqueId, argument);
488                         for (int i = 0; i < eventValidationPos; i++)
489                                 if (eventValidationValues [i] == hash)
490                                         return;
491                         eventValidationValues [eventValidationPos++] = hash;
492                 }
493
494                 public void ValidateEvent (string uniqueId)
495                 {
496                         ValidateEvent (uniqueId, null);
497                 }
498
499                 public void ValidateEvent (string uniqueId, string argument)
500                 {
501                         if (uniqueId == null || uniqueId.Length == 0)
502                                 throw new ArgumentException ("must not be null or empty", "uniqueId");
503                         if (!page.EnableEventValidation)
504                                 return;
505                         if (eventValidationValues == null)
506                                 goto bad;
507                         
508                         int hash = CalculateEventHash (uniqueId, argument);
509                         for (int i = 0; i < eventValidationValues.Length; i++)
510                                 if (eventValidationValues [i] == hash)
511                                         return;
512                         
513                         bad:
514                         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.");
515                 }
516 #endif
517                 void WriteScripts (HtmlTextWriter writer, ScriptEntry scriptList)
518                 {
519                         if (scriptList == null)
520                                 return;
521
522                         writer.WriteLine ();
523
524                         while (scriptList != null) {
525                                 switch (scriptList.Format) {
526                                 case ScriptEntryFormat.AddScriptTag:
527                                         EnsureBeginScriptBlock (writer);
528                                         writer.Write (scriptList.Script);
529                                         break;
530                                 case ScriptEntryFormat.Include:
531                                         EnsureEndScriptBlock (writer);
532                                         WriteClientScriptInclude (writer, scriptList.Script);
533                                         break;
534                                 default:
535                                         EnsureEndScriptBlock (writer);
536                                         writer.WriteLine (scriptList.Script);
537                                         break;
538                                 }
539                                 scriptList = scriptList.Next;
540                         }
541                         EnsureEndScriptBlock (writer);
542                 }
543
544                 bool _scriptTagOpened;
545
546                 void EnsureBeginScriptBlock (HtmlTextWriter writer) {
547                         if (!_scriptTagOpened) {
548                                 WriteBeginScriptBlock (writer);
549                                 _scriptTagOpened = true;
550                         }
551                 }
552
553                 void EnsureEndScriptBlock (HtmlTextWriter writer) {
554                         if (_scriptTagOpened) {
555                                 WriteEndScriptBlock (writer);
556                                 _scriptTagOpened = false;
557                         }
558                 }
559
560 #if NET_2_0
561                 internal void RestoreEventValidationState (string fieldValue)
562                 {
563                         if (!page.EnableEventValidation || fieldValue == null || fieldValue.Length == 0)
564                                 return;
565                         IStateFormatter fmt = page.GetFormatter ();
566                         eventValidationValues = (int []) fmt.Deserialize (fieldValue);
567                         eventValidationPos = eventValidationValues.Length;
568                 }
569                 
570                 internal void SaveEventValidationState ()
571                 {
572                         if (!page.EnableEventValidation)
573                                 return;
574
575                         string eventValidation = GetEventValidationStateFormatted ();
576                         if (eventValidation == null)
577                                 return;
578
579                         RegisterHiddenField (EventStateFieldName, eventValidation);
580                 }
581
582                 internal string GetEventValidationStateFormatted ()
583                 {
584                         if (eventValidationValues == null || eventValidationValues.Length == 0)
585                                 return null;
586
587                         if(page.IsCallback && !_hasRegisteredForEventValidationOnCallback)
588                                 return null;
589
590                         IStateFormatter fmt = page.GetFormatter ();
591                         int [] array = new int [eventValidationPos];
592                         Array.Copy (eventValidationValues, array, eventValidationPos);
593                         return fmt.Serialize (array);
594                 }
595
596                 internal string EventStateFieldName
597                 {
598                         get { return "__EVENTVALIDATION"; }
599                 }
600
601                 internal void WriteExpandoAttributes (HtmlTextWriter writer)
602                 {
603                         if (expandoAttributes == null)
604                                 return;
605
606                         writer.WriteLine ();
607                         WriteBeginScriptBlock (writer);
608
609                         foreach (string controlId in expandoAttributes.Keys) {
610                                 writer.WriteLine ("var {0} = document.all ? document.all [\"{0}\"] : document.getElementById (\"{0}\");", controlId);
611                                 ListDictionary attrs = (ListDictionary) expandoAttributes [controlId];
612                                 foreach (string attributeName in attrs.Keys) {
613                                         writer.WriteLine ("{0}.{1} = \"{2}\";", controlId, attributeName, attrs [attributeName]);
614                                 }
615                         }
616                         WriteEndScriptBlock (writer);
617                         writer.WriteLine ();
618                 }
619
620 #endif
621                 internal static void WriteBeginScriptBlock (HtmlTextWriter writer)
622                 {
623                         writer.WriteLine ("<script"+
624 #if !NET_2_0
625                                 " language=\"javascript\""+
626 #endif
627                                 " type=\"text/javascript\">");
628                         writer.WriteLine ("<!--");
629                 }
630
631                 internal static void WriteEndScriptBlock (HtmlTextWriter writer)
632                 {
633                         writer.WriteLine ("// -->");
634                         writer.WriteLine ("</script>");
635                 }
636                 
637                 internal void WriteHiddenFields (HtmlTextWriter writer)
638                 {
639                         if (hiddenFields == null)
640                                 return;
641
642                         writer.RenderBeginTag (HtmlTextWriterTag.Div);
643                         foreach (string key in hiddenFields.Keys) {
644                                 string value = hiddenFields [key] as string;
645                                 writer.WriteLine ("<input type=\"hidden\" name=\"{0}\" id=\"{0}\" value=\"{1}\" />", key, HttpUtility.HtmlAttributeEncode (value));
646                         }
647                         writer.RenderEndTag (); // DIV
648                         hiddenFields = null;
649                 }
650                 
651                 internal void WriteClientScriptInclude (HtmlTextWriter writer, string path) {
652                                         if (!page.IsMultiForm)
653                                                 writer.WriteLine ("<script src=\"{0}\" type=\"text/javascript\"></script>", path);
654                                         else {
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>");
663                                         }
664                 }
665                 
666                 internal void WriteClientScriptBlocks (HtmlTextWriter writer)
667                 {
668                         WriteScripts (writer, clientScriptBlocks);
669                 }
670         
671                 internal void WriteStartupScriptBlocks (HtmlTextWriter writer)
672                 {
673                         WriteScripts (writer, startupScriptBlocks);
674                 }
675         
676                 internal void WriteArrayDeclares (HtmlTextWriter writer)
677                 {
678                         if (registeredArrayDeclares != null) {
679                                 writer.WriteLine();
680                                 WriteBeginScriptBlock (writer);
681                                 IDictionaryEnumerator arrayEnum = registeredArrayDeclares.GetEnumerator();
682                                 while (arrayEnum.MoveNext()) {
683                                         if (page.IsMultiForm)
684                                                 writer.Write ("\t" + page.theForm + ".");
685                                         else
686                                                 writer.Write ("\tvar ");
687                                         writer.Write(arrayEnum.Key);
688                                         writer.Write(" =  new Array(");
689                                         IEnumerator arrayListEnum = ((ArrayList) arrayEnum.Value).GetEnumerator();
690                                         bool isFirst = true;
691                                         while (arrayListEnum.MoveNext()) {
692                                                 if (isFirst)
693                                                         isFirst = false;
694                                                 else
695                                                         writer.Write(", ");
696                                                 writer.Write(arrayListEnum.Current);
697                                         }
698                                         writer.WriteLine(");");
699                                 }
700                                 WriteEndScriptBlock (writer);
701                                 writer.WriteLine ();
702                         }
703                 }
704
705 #if NET_2_0
706                 internal string GetClientValidationEvent (string validationGroup) {
707                         if (page.IsMultiForm)
708                                 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate('" + validationGroup + "');";
709                         return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate('" + validationGroup + "');";
710                 }
711 #endif
712
713                 internal string GetClientValidationEvent ()
714                 {
715                         if (page.IsMultiForm)
716                                 return "if (typeof(" + page.theForm + ".Page_ClientValidate) == 'function') " + page.theForm + ".Page_ClientValidate();";
717                         return "if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate();";
718                 }
719
720
721                 internal string WriteSubmitStatements ()
722                 {
723                         if (submitStatements == null) return null;
724                         
725                         StringBuilder sb = new StringBuilder ();
726                         ScriptEntry entry = submitStatements;
727                         while (entry != null) {
728 #if NET_2_0
729                                 sb.Append (EnsureEndsWithSemicolon (entry.Script));
730 #else
731                                 sb.Append (entry.Script);
732 #endif
733                                 entry = entry.Next;
734                         }
735 #if NET_2_0
736                         RegisterClientScriptBlock (GetType(), "HtmlForm-OnSubmitStatemen",
737 @"
738 " + (page.IsMultiForm ? page.theForm + "." : null) + @"WebForm_OnSubmit = function () {
739 " + sb.ToString () + @"
740 return true;
741 }
742 ", true);
743                         if (page.IsMultiForm)
744                                 return "javascript:return " + page.theForm + ".WebForm_OnSubmit();";
745                         else
746                                 return "javascript:return WebForm_OnSubmit();";
747
748 #else
749                         return sb.ToString ();
750 #endif
751                 }
752                 
753                 internal static string GetScriptLiteral (object ob)
754                 {
755                         if (ob == null)
756                                 return "null";
757                         else if (ob is string) {
758                                 string s = (string)ob;
759                                 bool escape = false;
760                                 int len = s.Length;
761
762                                 for (int i = 0; i < len; i++)
763                                         if (s [i] == '\\' || s [i] == '\"') {
764                                                 escape = true;
765                                                 break;
766                                         }
767
768                                 if (!escape)
769                                         return string.Concat ("\"", s, "\"");
770
771                                 StringBuilder sb = new StringBuilder (len + 10);
772
773                                 sb.Append ('\"');
774                                 for (int si = 0; si < len; si++) {
775                                         if (s [si] == '\"')
776                                                 sb.Append ("\\\"");
777                                         else if (s [si] == '\\')
778                                                 sb.Append ("\\\\");
779                                         else
780                                                 sb.Append (s [si]);
781                                 }
782                                 sb.Append ('\"');
783
784                                 return sb.ToString ();
785                         } else if (ob is bool) {
786                                 return ob.ToString ().ToLower (CultureInfo.InvariantCulture);
787                         } else {
788                                 return ob.ToString ();
789                         }
790                 }
791
792                 sealed class ScriptEntry
793                 {
794                         public readonly Type Type;
795                         public readonly string Key;
796                         public readonly string Script;
797                         public readonly ScriptEntryFormat Format;
798                         public ScriptEntry Next;
799
800                         public ScriptEntry (Type type, string key, string script, ScriptEntryFormat format) {
801                                 Key = key;
802                                 Type = type;
803                                 Script = script;
804                                 Format = format;
805                         }
806                 }
807
808                 enum ScriptEntryFormat
809                 {
810                         None,
811                         AddScriptTag,
812                         Include,
813                 }
814
815 #if NET_2_0
816                 // helper method
817                 internal static string EnsureEndsWithSemicolon (string value) {
818                         if (value != null && value.Length > 0 && value [value.Length - 1] != ';')
819                                 return value += ";";
820                         return value;
821                 }
822 #endif
823         }
824 }