views:

27

answers:

1

Hello,

I wrote a Simple Control, that implements ScriptControl. This is holder for JQuery framework:

 /// <summary>
/// Generic control with client behavior handled via jQuery
/// </summary>
public abstract partial class JQControl : ScriptControl, INamingContainer
{
    /// <summary>
    /// Client method to be called after jQuery initialization
    /// </summary>
    [PersistenceMode(PersistenceMode.InnerProperty)]
    public JQRaw AfterInit
    {
        get;
        set;
    }

    /// <summary>
    /// Client method to be called before jQuery initialization
    /// </summary>
    [PersistenceMode(PersistenceMode.InnerProperty)]
    public JQRaw PreInit
    {
        get;
        set;
    }

    /// <summary>
    /// Any data to initialize the control with (name-value pairs)
    /// </summary>
    public IDictionary<string, object> InitData
    {
        get; set;
    }

    /// <summary>
    /// Authorization templates to be registered and used by privilege manager
    /// </summary>
    public IDictionary<string, AuthorizationTemplate> AuthorizationTemplates
    {
        get; set;
    }

    /// <summary>
    /// If ThemePath is specified, this css file will be looked for and loaded from the theme folder
    /// </summary>
    protected string ThemeCssName
    {
        get; set;
    }

    private string _themePath;

    /// <summary>
    /// Specifies path to look for custom css and images to enable theming.
    /// </summary>
    public string ThemePath
    {
        get
        {
            return _themePath == null ? DefaultThemeHelper.ThemeName : ResolveClientUrl(_themePath);
        }
        set
        {
            _themePath = value;
        }
    }

    /// <summary>
    /// Collection of streamed javascript files
    /// </summary>
    private readonly List<ScriptReference> scriptRefs = new List<ScriptReference>();

    /// <summary>
    /// Collection of streamed stylesheet files
    /// </summary>
    private readonly List<string> cssRefs = new List<string>();

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        FillScriptReferences();
    }

    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);

    }
    protected bool useDynamicCss = true;




    private bool dynamicCssEnabled;
    protected void EnableDynamicCss()
    {
        if(!dynamicCssEnabled)
        {
            //enable dynamic css loading
            addScriptReference(typeof(JQControl), "LWM.Implementation.Controls.DynamicStylesheet.css.js");
            dynamicCssEnabled = true;
        }
    }

    protected virtual void FillScriptReferences()
    {
        Type t = typeof(JQControl);
        addScriptReference(t, "LWM.Implementation.Controls.JScripts.Jquery.js");
        addScriptReference(t, "LWM.Implementation.Controls.JScripts.ExtendJQuery.js");
        addScriptReference(t, "LWM.Implementation.Controls.JScripts.ExtendAJAXDotNet.js");
        addScriptReference(t, "LWM.Implementation.Controls.JQ.ChainRequests.js");
        addScriptReference(t, "LWM.Implementation.Controls.JScripts.jquery.jcache.js");
        addScriptReference(t, "LWM.Implementation.Controls.JScripts.jquery.cookie.js");
        addScriptReference(t, "LWM.Implementation.Controls.JScripts.jquery.offset.js");
        //if ((_themePath != null && ThemeCssName != null))
        {
            EnableDynamicCss();                
        }
    }

    //added to render automatically assigned id, otherwise escaped
    protected override void AddAttributesToRender(HtmlTextWriter writer)
    {
        const string extCss = "jqcontrol";
        this.CssClass = this.CssClass != null ? this.CssClass + " " + extCss : extCss;

        base.AddAttributesToRender(writer);
        writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);
        //writer.AddAttribute(HtmlTextWriterAttribute.Class, CssClass);
    }

    /// <summary>
    /// Ataches an embedded script refernnce using its full name
    /// </summary>
    /// <param name="name">Name of the reference</param>
    /// <param name="useBaseClassAssembly">True if using base class assembly</param>
    protected void AttachScriptRefernceByName(string name, bool useBaseClassAssembly)
    {
        //current type
        Type type = this.GetType();
        if (useBaseClassAssembly)
        {
            //base type
            type = type.BaseType;
        }
        addScriptReference(type, name);
    }

    private void _addCssReference(Type type, string name)
    {
        string assembly = type.Assembly.FullName;
        Type t = null;
        if (BoostHelper.AdjustResourceParts(this, type.Assembly, ref assembly, ref name, ref t))
        {
           //  LoggerHelper.LogInfo(String.Format("CSS init with- Client Script:{0}, Type:{1}, Name:{2} ", this.Page.ClientScript.ToString(),t.ToString(),name),"LWMSiteResourceBooster");

            var sr = new DynamicStylesheetScriptReference(this.Page.ClientScript, t, new[] { name });    //(name, assembly);

            if (!scriptRefs.Contains(sr))
            {
                scriptRefs.Add(sr);
            }
        }
        else
        {
            string url = name;  // GetEmbeddedURL(type, name);
            if (!cssRefs.Contains(url))
            {
                cssRefs.Add(url);
            }
        }
    }

    //protected void addCssReference(string name, Type type)
    //{
    //    _addCssReference(type, name);
    //    if (cssRefs.Count > 0)
    //    {
    //        scriptRefs.Add(new DynamicStylesheetScriptReference(this.Page.ClientScript, type, cssRefs.ToArray()));
    //        cssRefs.Clear();
    //    }
    //}

    protected void addCssReference(Type type, params string[] names)
    {
        if (names == null) return;
        foreach (string name in names)
        {
            _addCssReference(type, name);
        }
        if(cssRefs.Count > 0)
        {
            scriptRefs.Add(new DynamicStylesheetScriptReference(this.Page.ClientScript, type, cssRefs.ToArray()));
            cssRefs.Clear();
        }
    }


    protected void removeCssReference(Type type, string name)
    {
        //TODO
        //if (cssRefs.Contains(url))
        //{
        //    cssRefs.Remove(url);
        //}
    }

    protected void addScriptReference(Type type, string name)
    {


        //full name of the current assembly            
        string assembly = type.Assembly.FullName;
        Type t = null;
        BoostHelper.AdjustResourceParts(this, type.Assembly, ref assembly, ref name, ref t);
        var sr = new HumanReadableScriptReference(name, assembly);
        if (!scriptRefs.Contains(sr))
        {
            scriptRefs.Add(sr);
        }
    }

    protected void removeScriptReference(Type type, string name)
    {
        //full name of the current assembly            
        string assembly = type.Assembly.FullName;
        Type t = null;
        BoostHelper.AdjustResourceParts(this, type.Assembly, ref assembly, ref name, ref t);
        var sr = new HumanReadableScriptReference(name, assembly);
        if (scriptRefs.Contains(sr))
        {
            scriptRefs.Remove(sr);
        }
    }

    private string _jqBreadCrumb;

    /// <summary>
    /// Unique jQuery pattern that identifies this control
    /// </summary>
    protected string JQBreadCrumb
    {
        get
        {
            if (_jqBreadCrumb == null)
            {
                //StringBuilder sb = new StringBuilder();
                //Control c = this;
                Control c = this.NamingContainer;
                //int step = 1;
                while (c != null && c.ClientID != "__Page")
                {
                    if (c is JQControl) // || step > 1)
                    {
                        _jqBreadCrumb = c.ClientID;
                        break;
                        //sb.Insert(0, " ");
                        //sb.Insert(0, "#" + c.ClientID);
                    }
                    c = c.NamingContainer;
                    //step++;
                }
                //_jqBreadCrumb = sb.ToString();
            }
            return _jqBreadCrumb;
        }
    }

    //regular expression to look for tokens
    private static readonly Regex tokenRegex = new Regex(
        @"##(?<tokenName>[\w\:]+)"
        );



    //actually replaces tokens
    private string tokenReplacer(Match m, bool useContext)
    {
        //token found
        string tokenName = m.Groups["tokenName"].Value;
        string ctlName = null;
        //check if we have a resource token
        if (ResourceHelper.CheckStringForToken(tokenName))
        {
            return "\"" + ResourceHelper.GetStringByToken(tokenName, ResourceType.Portal) + "\"";
        }
        switch (tokenName)
        {
            case "this":
                //special case: seek for the current control
                if (useContext) return "$(__$)";
                ctlName = "#" + this.ClientID; // JQBreadCrumb;
                break;
            case "parent":
                //special case: seek for the direct parent control
                ctlName = "#" + JQBreadCrumb;
                // this.NamingContainer.ClientID;   //NOTE: use parent breadcrumb here
                break;
            default:
                //seek for the child control with the given name
                if (!useContext)
                {
                    Control ctl = getChildByName(tokenName);
                    if (ctl != null) ctlName = "#" + ctl.ClientID;
                }
                //else ctlName = "[id^='" + this.ClientID + "_']" + "[id$='_" + tokenName + "']:first";
                break;
        }
        if(ctlName != null) return "$(\"" + ctlName + "\")";
        if (!useContext) return "$(this)._cc('" + tokenName + "', '" + this.ClientID + "')";
        return "$(__$)._cc('" + tokenName + "')";
    }

    private string tokenReplacerWithContext(Match m)
    {
        return tokenReplacer(m, true);
    }

    private string tokenReplacerWithoutContext(Match m)
    {
        return tokenReplacer(m, false);
    }

    protected virtual Control getChildByName(string name)
    {
        return this.Controls.Cast<Control>().SingleOrDefault(c => c.ID == name);
    }

    //regular expression to insert context holder
    private static readonly Regex contextRegex = new Regex(
        @"^(\s*function\(\s*\)\s*{)", RegexOptions.Multiline
        );

    private static int replacedNum;
    private static string contextReplacer(Match m)
    {
        replacedNum++;
        return "function() { var __$ = this; \r\n";
    }

    /// <summary>
    /// Substitue occurences of tokens of type ##[token] into corresponding jQuery calls
    /// </summary>
    /// <param name="callbackMethod">Callback method definition that contains tokens</param>
    /// <param name="useContext">True if local context is to be used on the client</param>
    protected void PrepareCallbackMethod(JQRaw callbackMethod, bool useContext)
    {
        if(useContext)
        {
            replacedNum = 0;
            callbackMethod.JQRawContent = contextRegex.Replace(callbackMethod.JQRawContent, contextReplacer);
            if(replacedNum == 0) useContext = false;
        }
        if (callbackMethod != null)
        {
            MatchEvaluator me;
            if(useContext) me = tokenReplacerWithContext;
            else me = tokenReplacerWithoutContext;

            //find tokens in callback body and replace them
            callbackMethod.JQRawContent = tokenRegex.Replace(callbackMethod.JQRawContent, me);
        }
    }

    protected void PrepareCallbackMethod(JQRaw callbackMethod)
    {
        PrepareCallbackMethod(callbackMethod, false);
    }

    protected virtual void PrepareCallbackMethods(object @params)
    {
        foreach (PropertyInfo pi in @params.GetType().GetProperties().Where(
                p => p.PropertyType.Equals(typeof(JQRaw))
            ))
        {
            if (pi.GetCustomAttributes(typeof(CallbackMethodAttribute), false).Length > 0)
            {
                //property has the attribute: prepare
                JQRaw pty = (JQRaw)pi.GetValue(@params, null);
                if (pty != null && pty.JQRawContent != null)
                {
                    PrepareCallbackMethod(pty);
                }
            }
        }
    }

    /// <summary>
    /// Returns the script files for the control
    /// </summary>
    /// <returns>Collection that contains ECMAScript (JavaScript) files that have been registered as embedded resources</returns>
    protected override IEnumerable<ScriptReference> GetScriptReferences()
    {
        return scriptRefs;
    }

    private const string selfAlias = "__$";
    protected virtual string GetDescriptorSelector()
    {
        //return this.ClientID;
        return selfAlias;
    }

    protected virtual IEnumerable<ScriptDescriptor> _getScriptDescriptors()
    {
        yield break;
    }

    //private const string afterInitTemplate = "function() {{ var _method = {0}; _method(); $('#{1}').trigger('_loadComplete'); }}";

    protected sealed override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        var result = new List<ScriptDescriptor>();

        //yield return new JQSelfDescriptor(this.ClientID, selfAlias);
        result.Add(new JQSelfDescriptor(this.ClientID, selfAlias));


        if (PreInit != null)
        {
            //to be executed before initialization
            PrepareCallbackMethod(PreInit, true);
            //yield return new JQDescriptor(GetDescriptorSelector(), "each", true, PreInit);
            result.Add(new JQDescriptor(GetDescriptorSelector(), "each", true, PreInit));
        }

        if(InitData != null)
        {
            foreach (string key in InitData.Keys)
            {
                //initialize with the given object as data
                object data;
                bool useStd = false;
                if (InitData[key] is NativeContainer)
                {
                    data = ((NativeContainer)InitData[key]).Content;
                    useStd = true;
                }
                else data = InitData[key];
                JQDescriptor jdesc = new JQDescriptor(GetDescriptorSelector(), "data", true, key, data);
                if(useStd) jdesc.UseStandardSerializer = true;
                    //yield return jdesc;
                    result.Add(jdesc);
            }
        }

        if (AuthorizationTemplates != null)
        {
            //register authorization templates
            //yield return new JQDescriptor(GetDescriptorSelector(), "pm_registerAuthTemplates", true, AuthorizationTemplates);
            result.Add(new JQDescriptor(GetDescriptorSelector(), "pm_registerAuthTemplates", true, AuthorizationTemplates));
        }

        //enumerate overriden method results
        foreach(ScriptDescriptor sd in _getScriptDescriptors())
        {
           // yield return sd;
            result.Add(sd);
        }

        if (AfterInit != null)
        {
            //to be executed after initialization
            PrepareCallbackMethod(AfterInit, true);
            //AfterInit.JQRawContent = String.Format(afterInitTemplate, AfterInit.JQRawContent, GetDescriptorSelector());
            //yield return new JQDescriptor(GetDescriptorSelector(), "each", true, AfterInit);
            result.Add(new JQDescriptor(GetDescriptorSelector(), "each", true, AfterInit));
        }

        if (ThemePath != null && ThemeCssName != null)
        {
            //load css file dynamically
            string cssURL = VirtualURLHelper.Combine(ThemePath, ThemeCssName);
            DynamicStylesheetDescriptor dsdesc = new DynamicStylesheetDescriptor(cssURL);
            //yield return dsdesc;
            result.Add(dsdesc);
        }

        if (cssRefs.Count > 0)
        {
            DynamicStylesheetDescriptor dsdesc = new DynamicStylesheetDescriptor(cssRefs);
            //yield return dsdesc;
            result.Add(dsdesc);
        }

        //final trigger
        //yield return new JQDescriptor(GetDescriptorSelector(), "trigger", "_loadComplete");

        //yield break;
        return result;
    }

    /// <summary>
    /// Helper method to create URL to an embedded resource
    /// </summary>
    /// <param name="type">The type of the server-side resource</param>
    /// <param name="resourceName">The name of the server-side resource</param>
    /// <returns>The URL reference to the resource</returns>
    protected string GetEmbeddedURL(Type type, string resourceName)
    {
        //get base URL
        string url = Page.ClientScript.GetWebResourceUrl(type, resourceName);
        //attach name of the resource
        return url.Replace("?", "?name=" + resourceName + "&");
    }
}

I place it on page into ascx Control: Page: `<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="p2.aspx.cs" Inherits="LWM.Implementation.Portal.Sample.TestOutputCache.p2" %>

<%@ Register TagPrefix="LWM" Src="~/Sample/TestOutputCache/testControl2.ascx" TagName="TestControl2"%>

<LWM:TestControl2 ID="testCached" runat="server" />


</div>
</form>

`

ascx: `<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="testControl2.ascx.cs" Inherits="LWM.Implementation.Portal.Sample.testControl2" %> <%@ OutputCache Duration="600" VaryByParam="None" %> <%@ Register TagPrefix="Controls" Assembly="LWM.Implementation.Controls" Namespace="LWM.Implementation.Controls.JQComposite" %> Test 2 function (evt) {

        console.log("afterInit 2");
    }
</AfterInit>

`

Also i enable ascx control cache. When page first loaded all ok, but when page getted from server cache all script references are missing...

I search a lot and can't find any idea. So, problem is that scriptmanager do not generate script reference when control loaded from server cache.

A: 

This is bug with OutputCaching and Script Manager, which officially was recognized by Microsoft

There is workaround to add scripts to scriptmanager manually:

 <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Name="AjaxControlToolkit.Common.Common.js" Assembly="AjaxControlToolkit" />
            <asp:ScriptReference Name="AjaxControlToolkit.ExtenderBase.BaseScripts.js" Assembly="AjaxControlToolkit" />
            <asp:ScriptReference Name="AjaxControlToolkit.TextboxWatermark.TextboxWatermark.js"
                Assembly="AjaxControlToolkit" />
        </Scripts>
    </asp:ScriptManager>
Alexander