Showing posts with label JQuery. Show all posts
Showing posts with label JQuery. Show all posts

Saturday, October 12, 2013

jQuery dynamic validation error messages

I am not a champ at asp.net mvc and found myself struggling with a task that at first I thought will be trivial, below is how I got through it :)

I wrote a custom validation attribute and needed to show validation messages as per values entered. Below is stripped down version of what I ended up doing:

Below lines add validation attribute named checkagainstmaxsumvalue that checks values of two fields and decides error messages dynamically

jQuery.validator.unobtrusive.adapters.add("checkagainstmaxsumvalue", ["dependentproperty1", "dependentproperty2"], function (options) {

    options.rules['checkagainstmaxsumvalue'] = {
        prop1: options.params['dependentproperty1'],
        prop2: options.params['dependentproperty2']
    };

});

jQuery.validator.addMethod("checkagainstmaxsumvalue", function (value, element, params) {
        var result = true;
        var prop1 = parseInt($('#' + params['prop1']).val(), 10);
        var prop2 = parseInt($('#' + params['prop2']).val(), 10);

        var errMsg = getErrorMessage(prop1, prop2);
        if (errMsg) {
            result = false;
        }
        return result;

    }, function (params, element) {
        var prop1 = parseInt($('#' + params['prop1']).val(), 10);
        var prop2 = parseInt($('#' + params['prop2']).val(), 10);
        return getErrorMessage(prop1, prop2);
    }
);

//Validation logic here, returns error message if validation fails 
//and empty string if success
function getErrorMessage(prop1, prop2) {
    var errMsg = "";
    if (prop1 < 20 && prop2 < 20) {
        errMsg = "Either of prop1 and prop2 should be greater than 20";
    } else if (prop1 + prop2 > 30) {
        errMsg = "Sum of prop1 and prop2 should be less than equal to 30";
    }
    return errMsg;

}

This all I did after reading below answer at every developer's life saver site SO :)
http://stackoverflow.com/questions/13352626/dynamic-jquery-validate-error-messages-with-addmethod-based-on-the-element#answer-13352987

This worked perfectly fine in my case but still there is concern that validation logic executes twice, once for deciding message and once for actual verification. This sounds a bit overkill but till now I didn't have a better option.

Thursday, March 29, 2012

search as you type functionality for ASP.NET site using JQuery and ASMX web services

This post is for search as you type functionality (on demand loading) for ASP.NET site using JQuery and ASMX web services.

Approach:
  • Step 1: Handle textbox keydown event to get user input(‘keyup’ could have been easier choice but it has a problem, will tell u that later in this post)
  • Step 2: Send an AJAX hit to server to get search results corresponding to user input.
  • Step 3: Catch and display result on client side
  • Step 4: Separate handling for down/up key for traversing between search results, enter and tab keys for selecting specific search result and escape key to remove search result.
Cool things to look for:
  • CSS stuff (z-index, etc.) that give search results google search type floating feel (exaggerating a bit to keep your interest here ;)
  • AJAX calls to ASMX web service.
 
 Code blocks: (Download Code)

ASPX (Just a textbox to type and a div to hold results)  
< span style="color:White;">Search As You Type:
        < input id="txtTest" type="text" style="width: 400px;" />
        < div id="divPredictedResultsContainer" class="divPredictedResultsContainer" >


Jquery (Code to handle keydown event and make Ajax hits with entered text)
<script type="text/javascript">
        $(document).ready(function() {
            var selectedResultCounter = 1;
            $("#txtTest").keydown(function(event) {
                //check if key pressed is alphabet, a number or backspace
                if ((event.keyCode > 64 && event.keyCode < 91) || (event.keyCode > 47 && event.keyCode < 58) || (event.keyCode == 8)) { //handle alphabets, numbers and backspace
                    var currentValue = ''
                    if (event.keyCode == 8 && $('#txtTest').val().length > 0) {
                        currentValue = $('#txtTest').val().substring(0, $('#txtTest').val().length - 1);
                    } else {
                        currentValue = $('#txtTest').val() + String.fromCharCode(event.keyCode);
                    }

                    selectedResultCounter = 1;
                    $.ajax({
                        type: "POST",
                        url: "GetData.asmx/GetData",
                        data: "{'startsWith':'" + currentValue + "'}",
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success: function(msg) {
                            $("#divPredictedResultsContainer").html(msg.d);
                            if (msg.d.indexOf("divPredictedResultStyle") > -1) {
                                $("#divPredictedResultsContainer").addClass("roundedCorners");
                            }
                        },
                        error: function(xhr, ajaxOptions, thrownError) {
                            alert(xhr.status);
                            alert(thrownError);
                        }
                    });
                } else if (event.keyCode == 40) {       //handle down key
                    var resultCount = $(".divPredictedResultStyle").size();
                    if (selectedResultCounter == resultCount + 1) {
                        selectedResultCounter = 1;
                        $("#divPredictedResult" + resultCount).removeClass("divPredictedResultSelected");
                    }
                    $("#divPredictedResult" + selectedResultCounter).addClass("divPredictedResultSelected");
                    $("#divPredictedResult" + (selectedResultCounter - 1)).removeClass("divPredictedResultSelected");
                    selectedResultCounter = selectedResultCounter + 1;

                } else if (event.keyCode == 38) {       //handle up key
                    selectedResultCounter = selectedResultCounter - 1;
                    var resultCount = $(".divPredictedResultStyle").size();
                    if (selectedResultCounter == 1) {
                        selectedResultCounter = resultCount + 1;
                        $("#divPredictedResult1").removeClass("divPredictedResultSelected");
                    }
                    $("#divPredictedResult" + (selectedResultCounter - 1)).addClass("divPredictedResultSelected");
                    $("#divPredictedResult" + selectedResultCounter).removeClass("divPredictedResultSelected");
                } else if (event.keyCode == 9 || event.keyCode == 13) {       //handle tab key and enter key
                    if (event.preventDefault) {                               //this is to select one of the records
                        event.preventDefault();                               //from search results
                        event.stopPropagation();
                    }
                    $('#txtTest').val($(".divPredictedResultSelected").html());
                    return false;
                } else if (event.keyCode == 27) {                           //handle escape key
                    $(".divPredictedResults").hide();                       //hide results div, if escape key is pressed
                    $("#divPredictedResultsContainer").removeClass("roundedCorners");
                }
            });
        });
       
    script>
Points to ponder (read these points only if u r not in hurry, else move to ASMX code block :)
      • ·         Here if I would have used keyup event in place of keydown, It wouldn’t have been possible to select result by pressing tab or enter key as in that case, event has already been occurred thus prevent default can’t work.
      • ·         Code in red (see above) is a known bug :( u can c it as coding exercise): This code block handles backspace key but assumes that user will press it end of text only and not from between. I have this problem because on keydown I don’t have final value of textbox, I just have the keycode of key pressed, so I am forced to assume that key pressed is at end only(I’ll update this post once I find the proper solution).
      • ·         Code in green is heart of this all, it sends request to an ASMX service and captures response coming from there.
      • ·         In code blocks that handle up/down key there is logic to keep selecting search result records in loop, means if down key is pressed and last result found was currently selected then automatically selection will get changed to first search result.
 
ASMX Service code (To serve results from server)
< WebMethod() > _
    Public Function GetData(ByVal startsWith As String) As String
        Dim strResults As New StringBuilder(String.Empty)
        Dim dtResults As DataTable = GetDataForIntellisense(startsWith)
        Dim counter As Integer = 1
        strResults.Append("< div class='divPredictedResults'>")
        If dtResults.Rows.Count > 0 Then
            For Each dr As DataRow In dtResults.Rows
                strResults.Append("< div class='divPredictedResultStyle' id='divPredictedResult" + counter.ToString() + "'>" + dr("code").ToString() + "")
                counter += 1
            Next
        Else
            strResults.Append("No results found.")
        End If
        strResults.Append("")
        Return strResults.ToString()
    End Function

There is nothing typical in above ASMX code, its just about fetching records from database and then generating html corresponding to records retreived. One thing worth noticing is that one div is created corresponding to each search result and they have been given ids in incremental order. Other thing is method ‘GetDataForIntellisense’, implementation of this is upon you. Current code expects this method to return a datable with a column named “code”.


CSS (last but not least)
In above css, class ‘divPredictedResultsContainer’ has two lesser known attributes, namely z-index and border-radius.
Z-index is to specify stack order of an element. It lets results to float above other contents and thus you don’t need to reserve space for search results and can show search results without disturbing other contents of site.
Border-radius property is for giving rounded corners effect to div. This might not work in older browsers.


Download sample code from here.

About Me

My photo
Delhi, India
Fun, music, travel and nature loving, always smiling, computer addict!!