File: UI\ScriptComponentDescriptor.cs
Project: ndp\fx\src\xsp\system\Extensions\System.Web.Extensions.csproj (System.Web.Extensions)
//------------------------------------------------------------------------------
// <copyright file="ScriptComponentDescriptor.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI {
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Web;
    using System.Web.Resources;
    using System.Web.Script.Serialization;
 
    public class ScriptComponentDescriptor : ScriptDescriptor {
        // PERF: In the ScriptControl/Properties/Scenario.aspx perf test, SortedList is 2% faster than
        // SortedDictionary.
        private string _elementIDInternal;
        private SortedList<string, string> _events;
        private string _id;
        private SortedList<string, Expression> _properties;
        private bool _registerDispose = true;
        private JavaScriptSerializer _serializer;
        private string _type;
 
        public ScriptComponentDescriptor(string type) {
            if (String.IsNullOrEmpty(type)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "type");
            }
            _type = type;
        }
 
        // Used by ScriptBehaviorDescriptor and ScriptControlDesriptor
        internal ScriptComponentDescriptor(string type, string elementID)
            : this(type) {
            if (String.IsNullOrEmpty(elementID)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "elementID");
            }
            _elementIDInternal = elementID;
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")]
        public virtual string ClientID {
            get {
                return ID;
            }
        }
 
        internal string ElementIDInternal {
            get {
                return _elementIDInternal;
            }
        }
 
        private SortedList<string, string> Events {
            get {
                if (_events == null) {
                    _events = new SortedList<string, string>(StringComparer.Ordinal);
                }
                return _events;
            }
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")]
        public virtual string ID {
            get {
                return _id ?? String.Empty;
            }
            set {
                _id = value;
            }
        }
 
        private SortedList<string, Expression> Properties {
            get {
                if (_properties == null) {
                    _properties = new SortedList<string, Expression>(StringComparer.Ordinal);
                }
                return _properties;
            }
        }
 
        internal bool RegisterDispose {
            get {
                return _registerDispose;
            }
            set {
                _registerDispose = value;
            }
        }
 
        private JavaScriptSerializer Serializer {
            get {
                if (_serializer == null) {
                    _serializer = new JavaScriptSerializer();
                }
                return _serializer;
            }
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
            Justification = "Refers to a script element, not to Object.GetType()")]
        public string Type {
            get {
                return _type;
            }
            set {
                if (String.IsNullOrEmpty(value)) {
                    throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "value");
                }
                _type = value;
            }
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")]
        public void AddComponentProperty(string name, string componentID) {
            if (String.IsNullOrEmpty(componentID)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "componentID");
            }
            AddProperty(name, new ComponentReference(componentID));
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")]
        public void AddElementProperty(string name, string elementID) {
            if (String.IsNullOrEmpty(elementID)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "elementID");
            }
            AddProperty(name, new ElementReference(elementID));
        }
 
        public void AddEvent(string name, string handler) {
            if (String.IsNullOrEmpty(name)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "name");
            }
            if (String.IsNullOrEmpty(handler)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "handler");
            }
            Events[name] = handler;
        }
 
        public void AddProperty(string name, object value) {
            AddProperty(name, new ObjectReference(value));
        }
 
        private void AddProperty(string name, Expression value) {
            if (String.IsNullOrEmpty(name)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "name");
            }
            Debug.Assert(value != null);
            Properties[name] = value;
        }
 
        public void AddScriptProperty(string name, string script) {
            if (String.IsNullOrEmpty(script)) {
                throw new ArgumentException(AtlasWeb.Common_NullOrEmpty, "script");
            }
            AddProperty(name, new ScriptExpression(script));
        }
 
        private void AppendEventsScript(StringBuilder builder) {
            // PERF: Use field directly to avoid creating Dictionary if not already created
            if (_events != null && _events.Count > 0) {
                builder.Append('{');
                bool first = true;
 
                // Can't use JavaScriptSerializer directly on events dictionary, since the values are
                // JavaScript functions and should not be quoted.
                foreach (KeyValuePair<string, string> e in _events) {
                    if (first) {
                        first = false;
                    }
                    else {
                        builder.Append(',');
                    }
                    builder.Append('"');
                    builder.Append(HttpUtility.JavaScriptStringEncode(e.Key));
                    builder.Append('"');
                    builder.Append(':');
                    builder.Append(e.Value);
                }
 
                builder.Append("}");
            }
            else {
                builder.Append("null");
            }
        }
 
        private void AppendPropertiesScript(StringBuilder builder) {
            bool first = true;
 
            // PERF: Use field directly to avoid creating Dictionary if not already created
            if (_properties != null && _properties.Count > 0) {
                foreach (KeyValuePair<string, Expression> p in _properties) {
                    if (p.Value.Type == ExpressionType.Script) {
                        if (first) {
                            builder.Append("{");
                            first = false;
                        }
                        else {
                            builder.Append(",");
                        }
                        builder.Append('"');
                        builder.Append(HttpUtility.JavaScriptStringEncode(p.Key));
                        builder.Append('"');
                        builder.Append(':');
                        p.Value.AppendValue(Serializer, builder);
                    }
                }
            }
 
            if (first) {
                // If we didn't see any properties, append "null"
                builder.Append("null");
            }
            else {
                // Else, close the JSON object
                builder.Append("}");
            }
        }
 
        private void AppendReferencesScript(StringBuilder builder) {
            bool first = true;
 
            // PERF: Use field directly to avoid creating Dictionary if not already created
            if (_properties != null && _properties.Count > 0) {
                foreach (KeyValuePair<string, Expression> p in _properties) {
                    if (p.Value.Type == ExpressionType.ComponentReference) {
                        if (first) {
                            builder.Append("{");
                            first = false;
                        }
                        else {
                            builder.Append(",");
                        }
                        builder.Append('"');
                        builder.Append(HttpUtility.JavaScriptStringEncode(p.Key));
                        builder.Append('"');
                        builder.Append(':');
                        p.Value.AppendValue(Serializer, builder);
                    }
                }
            }
 
            if (first) {
                // If we didn't see any references, append "null"
                builder.Append("null");
            }
            else {
                // Else, close the JSON object
                builder.Append("}");
            }
        }
 
        protected internal override string GetScript() {
            const string separator = ", ";
 
            if (!String.IsNullOrEmpty(ID)) {
                AddProperty("id", ID);
            }
 
            StringBuilder builder = new StringBuilder();
            builder.Append("$create(");
 
            builder.Append(Type);
 
            builder.Append(separator);
            AppendPropertiesScript(builder);
 
            builder.Append(separator);
            AppendEventsScript(builder);
 
            builder.Append(separator);
            AppendReferencesScript(builder);
 
            if (ElementIDInternal != null) {
                builder.Append(separator);
                builder.Append("$get(\"");
                builder.Append(HttpUtility.JavaScriptStringEncode(ElementIDInternal));
                builder.Append("\")");
            }
 
            builder.Append(");");
 
            return builder.ToString();
        }
 
        internal override void RegisterDisposeForDescriptor(ScriptManager scriptManager, Control owner) {
            if (RegisterDispose && scriptManager.SupportsPartialRendering) {
                // If partial rendering is supported, register a JavaScript statement
                // that will dispose this component if the UpdatePanel it is inside is
                // getting refreshed. Only components need this; controls are associated
                // with DOM elements so they get disposed through their 'dispose' expando.
                scriptManager.RegisterDispose(owner, "$find('" + ID + "').dispose();");
            }
        }
 
        private abstract class Expression {
            public abstract ExpressionType Type { get; }
            public abstract void AppendValue(JavaScriptSerializer serializer, StringBuilder builder);
        }
 
        private enum ExpressionType {
            Script,
            ComponentReference
        }
 
        private sealed class ComponentReference : Expression {
            private string _componentID;
 
            public ComponentReference(string componentID) {
                _componentID = componentID;
            }
 
            public override ExpressionType Type {
                get {
                    return ExpressionType.ComponentReference;
                }
            }
 
            public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) {
                builder.Append('"');
                builder.Append(HttpUtility.JavaScriptStringEncode(_componentID));
                builder.Append('"');
            }
        }
 
        private sealed class ElementReference : Expression {
            private string _elementID;
 
            public ElementReference(string elementID) {
                _elementID = elementID;
            }
 
            public override ExpressionType Type {
                get {
                    return ExpressionType.Script;
                }
            }
 
            public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) {
                builder.Append("$get(\"");
                builder.Append(HttpUtility.JavaScriptStringEncode(_elementID));
                builder.Append("\")");
            }
        }
 
        private sealed class ObjectReference : Expression {
            private object _value;
 
            public ObjectReference(object value) {
                _value = value;
            }
 
            public override ExpressionType Type {
                get {
                    return ExpressionType.Script;
                }
            }
 
            public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) {
                // DevDiv Bugs 96574: pass SerializationFormat.JavaScript to serialize to straight JavaScript,
                // so date properties are rendered as dates
                serializer.Serialize(_value, builder, JavaScriptSerializer.SerializationFormat.JavaScript);
            }
        }
 
        private sealed class ScriptExpression : Expression {
            private string _script;
 
            public ScriptExpression(string script) {
                _script = script;
            }
 
            public override ExpressionType Type {
                get {
                    return ExpressionType.Script;
                }
            }
 
            public override void AppendValue(JavaScriptSerializer serializer, StringBuilder builder) {
                builder.Append(_script);
            }
        }
    }
}