How to avoid writing messy JavaScript in ASP.NET code?

I am asking what is the best way to use Javascript with ASP.NET.

I don't know if this is best practice, but I am adding a javascript client side event inside the code. It works correctly, but is it best practice?

For example, I have a radio button with a radio button and I add a Javascript client side event to Page_Init. The page initialization can be called multiple times, so Javascript will be rendered every time Page_It is called.

Also, it is difficult to debug a long Javascript string. How can this be cleaner ... is there a way?

Check out an example of a variable containing Javascript:

scripts.Text += "<script type='text/javascript'>function ValidateDdl" + metachamp.ID +
"(sender, args) {  if(" + txtReason.ClientID + ".GetText() != '' ||" +
dynamicControl.ClientID +
".style.display == 'none' || HiddenFieldSaveError.Contains('" + metachamp.ID +
"') ){" + dynamicControl.ClientID + ".className='';HiddenFieldError.Remove(" +
metachamp.ID + ");" + errorImage.ClientID +
".SetClientVisible(false);args.IsValid = true;}else{var comboVal = document.getElementById('" +
Image9.ClientID + "'.substring(0,'" + Image9.ClientID +
"'.length - 6) + 'ddl').value ;if (comboVal != '0' ) {args.IsValid = true;HiddenFieldError.Remove(" +
metachamp.ID + ");" + validImage.ClientID +
".SetClientVisible(false);HiddenField.Remove('Bypass-' + '" +
metachamp.ID.ToString() + "');HiddenFieldSaveError.Remove(" + metachamp.ID +
");" + dynamicControl.ClientID + ".className='';" + errorImage.ClientID +
".SetClientVisible(false);}";

      

+3


source to share


3 answers


The very first step is to decouple JavaScript from code and value interpolation. Instead of dynamically building JavaScript, the approach should have a JavaScript function that provides arguments.

After the first phase we get something like (sorry partial translation, it hurt me) the following. Note the use of the close-build pattern; in real code, I would add this as a separate module.

function makeValidator(champId, opts) {
    return function (sender, args) {
        // Now this is when it gets harry..
        //
        // Use $get (and $find) inside ASP.NET, especially when
        // dealing with ASP.NET AJAX integration to find a control by ID.
        //
        // HOWEVER, the code uses what appears to be some DevExpress
        // controls and thus must be accessed.. differently, mainly either by
        //   1. `window[clientId]` or
        //   2. `ASPxClientControl.GetControlCollection().GetByName(id);`
        // This is just one of those icky things to deal with; I've shown usage
        // of the former and it may need to be applied to the other controls as well.
        //
        var reasonControl = window[opts.reasonId];        // DX control
        var dynamicControl = $get(opts.dynamicControlId); // normal ASP.NET/DOM
        var errorImage = window[opts.errorImageId];       // DX control
        if(reasonControl.GetText() != '' || dynamicControl.style.display == "none") {
            dynamicControl.className='';
            errorImage.SetClientVisible(false);
            args.IsValid = true;
        }
        // etc.
    }
}

      

It should be clear that JavaScript code is separate from any string interpolation. This is a normal function that, when called with certain arguments (defined by the API), has certain behavior. While there are different approaches to "loading / entering" this JavaScript (which matters when logging into UpdatePanels and nested / complex hierarchies), let's assume that it is currently being wrapped <script>

in the markup of the page.

Now let's hook up the validator to the control - this is completely bogus, but it shows the use of data binding and the actual creation of a JavaScript "call" in the code, we'll see why in a second. until the ClientIDs of the controls are assigned.)



<!-- Use of the DataBind Container/Eval may be useful, but ignoring that.. --!>
<control:BlahBlah Id="ImaControl"
                  OnClientValidate="<%# CreateValidator(ImaControl) %>"/>

      

Then go back to your code:

protected string CreateValidator(Control c) {
    var champId = c.ClientID; // example, not necessarily true

    // Then setup other values to supply to the function. While JSON is not
    // *exactly* like a JS object literal it is close enough so we Just Don't Care.
    // I prefer Json.NET from Newtonsoft, but the standard support is just fine.
    // (The champId could also be serialized here, but I chose to show passing
    //  two arguments, one NOT escaped; we assume champId doesn't contain \s or 's.)
    var opts = new JavaScriptSerializer().Serialize(new {
        reasonId = reasonControl.ClientID,
        dynamicControlId = dynamicControl.ClientID,
        errorImageId = Error9.ClientId
    });

    // The use of parenthesis and actual JavaScript returned depends on if the
    // client-side validation property takes JavaScript to execute (common) or if
    // it takes a function to execute later, as found in DevExpress/some libraries.
    // (Remember from above that makeValidator returns a new function.)

    // For DX/DevExpress:
    return string.Format("makeValidator('{0}', {1})", champId, opts);

    // Normal ASP.NET might look like this:
    return string.Format("return makeValidator('{0}', {1}).apply(this, arguments)",
                    champId, opts);
}

      

And what's the gist of it, the errors included. However, there are a number of variations on this approach (including the magic of the ASP.NET AJAX ScriptControl) and subtle factors to consider; of great importance for memorization and striving is:

Separate JavaScript code and use API to pass values.

+4


source


This is a classic question for any technology stack. To answer this question, I remember a couple of things:

  • Don't repeat yourself (which can be harder with WebForms)
  • Do one thing and do it well.

I found that the client side functionality falls into a couple of categories:

  • Form validations, which are often extensions to business rules that must be managed at the end of the code
  • Usability improvements such as dropdown menus, automatic use of text when moving focus away from the text field, etc.
  • User experience management, which is probably driven by business rules, which isn't easy to do on the back.

( Note: There may be several errors in the code below, but it should give you the basic idea)

Form validation with ASP.NET WebForms

This was the area of ​​greatest pain for me. I am currently experimenting with FluentValidation with WebForms and it is really good. My best advice regarding validation is: Don't use validators <asp:Foo />

!
... This is the reason people complain that WebForms is a copy and paste environment. It doesn't have to be this way. Before rapid sample code does not use the Data [Set | Table | Row] s! ... You get all the data, but none of it works. Use an ORM like Entity Framework or NHibernate and all your ASP pages deal with entity classes because then you can use something like FluentValidation:

App_Code / Models / Objects / Post.cs

namespace Project.Models.Entities
{
    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime? ModifiedAt { get; set; }
    }
}

      

App_Code / Models / Validators / PostValidator.cs

using FluentValidation;
using Project.Models.Entities;

namespace Project.Models.Validators
{

    public class PostValidator : AbstractValidator<Post>
    {
        public PostValidator()
        {
            RuleFor(p => p.Title)
                .NotEmpty()
                .Length(1, 200);

            RuleFor(p => p.Body)
                .NotEmpty();
        }
    }
}

      

Once you have the core entities and validators, use them in your code behind:

controls UserControl / PostControl.ascx.cs

namespace Project.UserControls
{
    public class PostControl : System.Web.UI.UserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Page.IsPostBack)
            {
                PostValidator validator = new PostValidator();
                Post entity = new Post()
                {
                    // Map form fields to entity properties
                    Id = Convert.ToInt32(PostId.Value),
                    Title = PostTitle.Text.Trim(),
                    Body = PostBody.Text.Trim()
                };
                ValidationResult results = validator.Validate(entity);

                if (results.IsValid)
                {
                    // Save to the database and continue to the next page
                }
                else
                {
                    BulletedList summary = (BulletedList)FindControl("ErrorSummary");

                    // Display errors to the user
                    foreach (var failure in results.Errors)
                    {
                        Label errorMessage = FindControl(failure.PropertyName + "Error") as Label;

                        if (errorMessage == null)
                        {
                            summary.Items.Add(new ListItem(failure.ErrorMessage));
                        }
                        else
                        {
                            errorMessage.Text = failure.ErrorMessage;
                        }
                    }
                }
            }
            else
            {
                // Display form
            }
        }

        ...
    }
}

      

UserControl / PostControl.ascx controls

<asp:BulletedList ID="ErrorSummary" runat="server" CssClass="Error-Summary" />

<p>
    <asp:Label ID="PostTitleLabel" AssociatedControlID="PostTitle" runat="server">* Title:</asp:Label>
    <asp:TextBox ID="PostTitle" runat="server" />
    <asp:Label ID="PostTitleError" runat="server" CssClass="Error" />
</p>

<p>
    <asp:Label ID="PostBodyLabel" AssociatedControlID="PostBody" runat="server">* Body:</asp:Label>
    <asp:TextBox ID="PostBody" runat="server" TextMode="MultiLine" />
    <asp:Label ID="PostBodyError" runat="server" CssClass="Error" />
</p>

<asp:HiddenField ID="PostId" runat="server" />

      

Add client side validation programmatically

Now that we have a solid foundation in C #, you can add HTML attributes to each of the form fields and use jQuery Validate to trigger some front end validation. You can loop through the FluentValidation rules programmatically:

PostValidator validator = new PostValidator();

foreach (var rule in validator.AsEnumerable())
{
    propertyRule = rule as FluentValidation.Internal.PropertyRule;

    if (propertyRule == null)
        continue;

    WebControl control = (WebControl)FindControl("Post" + propertyRule.PropertyName);

    foreach (var x in rule.Validators)
    {
        if (x is FluentValidation.Validators.NotEmptyValidator)
        {
            control.Attributes["required"] = "required";
        }
        else if (x is FluentValidation.Validators.MaximumLengthValidator)
        {
            var a = (FluentValidation.Validators.MaximumLengthValidator)x;

            control.Attributes["size"] = a.Max.ToString();
            control.Attributes["minlength"] = a.Min.ToString();
            control.Attributes["maxlength"] = a.Max.ToString();
        }

        ...
    }
}

      



Complex, multi-verification checks

Any validation that requires data from more than one field should not be processed on the client. Do it in C #. Trying to flatten this together in HTML and JavaScript in an ASP page becomes cumbersome and not enough to justify the additional overhead and maintenance issues.

Usability improvements

These JavaScript snippets help users and do little to enforce business rules. In an application I'm working on, whenever the user moves focus to a text field, every word must be capitalized, so "foo bar" becomes "Foo Bar". Sending JavaScript and events to help:

Scripts / foo.js (imported on every page)

$(document).on("focusout", "input[type=text][data-capitalize-disabled^=true]", function(event) {
    event.target.value = event.target.value.replace(/(^|\s+)[a-z]/g, function(match, $1) {
        return $1.toUpperCase();
    });
});

      

To disable this behavior:

Code for:

PostTitle.Attributes["data-capitalize-disabled"] = "true";

      

ASP:

<asp:TextBox ... data-capitalize-disabled="true" />

      

If you can manage this in an ASP file, you have now completely separated the front and back end code!

User interaction management

This is an 800 pound gorilla for forefoot development. I like to use a "widget template" here where you write a JavaScript class to capture the behavior and use HTML attributes and class names as hooks for JavaScript to do this.

<strong> Scripts /FooWidget.js

function FooWidget(element) {
    this.$element = $(element);
    this.fillOptions = this.fillOptions.bind(this);
    this.$element.on("click", "[data-action=fillOptions]", this.fillOptions);
}

FooWidget.prototype = {
    constructor: FooWidget,

    fillOptions: function(event) {
        // make ajax request:

        var select = this.$element.find("select:first")[0],
            option = null;

        option = document.createElement("option");
        option.value = "...";
        option.text = "...";
        select.appendChild(option);

        ...
    },

    focus: function() {
        this.$element.find(":input:first").focus();
    }
};

      

And in your ASP file:

<asp:Panel ID="FooPanel" runat="server">
    <button type="button" data-action="fillOptions">Fill Options</button>
    <asp:DropDownList ID="OptionsDropdown" runat="server" />
</asp:Panel>

<script type="text/javascript">
    var foo = new FooWidget("<%# FooPanel.ClientId %>");
</script>

      

Again, this is where you want to keep JavaScript and HTML together, not put JavaScript in C #.

+1


source


I found a nice solution for client side events using javascript.

So, basically I add the ClientSideEvent to the .ascx file. For example, I add the SelectedIndexChanged event. When the switch index changes, it calls the javascript function that is inside the .js file.

We'll see:

Client side event in .ascx

<dx:ASPxRadioButtonList runat="server" ID="rblistComment">
    <Items>
        <dx:ListEditItem Text="Nouvelle information" Value="0" />
        <dx:ListEditItem Text="Correction de valeurs" Value="1" />
        <dx:ListEditItem Text="Autre" Value="2" />
    </Items>
    <ClientSideEvents SelectedIndexChanged="rblistComment_SelectIndexChanged" />
</dx:ASPxRadioButtonList>

      

After that I add javascript inside a file named: ClientEvents.js

Add javascript code

function rblistComment_SelectIndexChanged(s,e) {

var btnOk = eval($("[id$=btnOK]").attr("id"));
var txtCommentPopup = eval($("[id$=txtCommentPopup]").attr("id"));

btnOk.SetEnabled(s.GetValue() != null);
txtCommentPopup.SetVisible(s.GetValue() == '2');

      

}

Finally, in the codebehind, I add this code to Page_Load. This way it registers the script and binds the custom control to the javascript file.

Link javascript file with custom control

const string csname = "ClientEvents";
const string csurl = "~/js/EtudeCliniqueScript/ClientEvents.js";
Type cstype = this.GetType();

ClientScriptManager cs = Page.ClientScript;

if (!cs.IsClientScriptIncludeRegistered(cstype, csname))
{
    cs.RegisterClientScriptInclude(cstype, csname, ResolveClientUrl(csurl));
}

      

+1


source







All Articles