Corey Coogan

Python, .Net, C#, ASP.NET MVC, Architecture and Design

  • Subscribe

  • Archives

  • Blog Stats

    • 110,193 hits
  • Meta

Archive for the ‘jQuery’ Category

Using jQuery to Dynamically Display a Slideout

Posted by coreycoogan on May 19, 2011


Here’s something I find myself needing to do often enough that it warrants a post. Here’s the situation…

I am dynamically displaying an unknown number of data items. The item itself is unimportant for this post, however, the item may contain some attributes that shouldn’t be displayed on the page all the time.  Perhaps it’s a  long description or information about how the item was last updated.  Whatever it is, this data is better to be hidden unless someone actually wants to see it.  This may be a good use case for some sort of panel that slides out when an “info” icon or “>>” button is clicked.

Making this happen with jQuery is pretty simple and consists of a few steps:

  1. Wire up a “click” event to the button or text from where the panel will slide.
  2. Using the clicked on element as an anchor, use jQuery’s offset() method to get the coordinates.
  3. Either clone an existing element, such as a DIV, or create one on the fly and assign the following CSS rules to it:
    1. Position – This should be set to absolute so the panel displays relative to its containing element.
    2. Top – The distance in pixels, from the top of the containing element, where the panel should be placed.
    3. Left – The distance in pixels, from the left of the containing element, where the panel should be placed.
  4. Append the new panel to the container that is holding the list of data items.
  5. Display the panel using a cool effect – or hide it.
 A couple of notes before we get to the example.  First, there are 2 ways to get an element’s position in jQuery – offset() and position().  You can read the descriptions to understand what they both do, but offset() typically works for me. I’m also using the ‘slide’ toggle effect from jQuery UI to slide my panel from left to right with the toggle() method.

Now for an example:


<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){

	//either get data from a service here or have it ready from server-side code

});

function showMoreInfo(anchor,itemId)
{
	//make a jQuery object
	anchor = $(anchor);

	//the dynamic ID of the moreInfo panel
	var panelId = 'moreInfo_' + itemId;
	//see if the panel already exists
	var info = anchor.parent().parent().find('#' + panelId);

	if(!info.length){
		//the panel doesn't exist, so create it

		//get the coordinates from our anchor
		var coords = anchor.offset();
		//clone our info panel, or use jQuery templates
		info = $('#moreInfoPanel').clone();

		info.find('#description').html('<b>Get this data based on the itemId</b>');
		info.find('#lastUpdated').html('<b>Get this data based on the itemId</b>');

		//set the new dynamic ID
		info.attr('id',panelId);

		//now set the CSS to make the panel display where we want
		info.css({
			position: 'absolute',
			top: coords.top-(info.height()),
			left: coords.left+anchor.width() + 15
		});
		//append the panel to the item div
		anchor.parent().parent().append(info);
	}

	//use the jquery UI slide definition
	info.toggle('slide',function(){
		//slide callback to change the button text depending on the state
		if(info.is(':visible')){
			anchor.html('<< Less'); 		}else{ 			anchor.html('More >>');
		}
	});

}
</script>

<body>

<div id='moreInfoPanel' style='display:none;background:silver;padding:20px;'>
	<div id='description'></div>
	<div id='lastUpdated'></div>
</div>

<div id='dataItems'>

	<!-- this is an example data item. this would be generated dynamically server side or from a web service -->
	<!-- jQuery Templates could be nice here -->
	<div id='dataItem_ITEMID' class='dataItem' style='border:1px solid black;width:200px'>
		<div style='text-align:right'>
			<!-- wire up the click event -->
			<button onclick='javascript:showMoreInfo(this,"ITEMID");'>More >></button>
		</div>
		<hr/>
		<div>
			Main content goes here
		</div>
		
	</div>
</div>

</body>

Advertisements

Posted in jQuery, UI | Tagged: , , , , , | Comments Off on Using jQuery to Dynamically Display a Slideout

Hide the X in the corner of a JQuery Dialog

Posted by coreycoogan on January 31, 2011


NOTE TO SELF:

Until you find a better way, here’s how you remove the X that closes a JQuery dialog box:

$( ‘a.ui-dialog-titlebar-close’ ).remove();

OR

Change the Style definition in the HEAD.

Posted in jQuery | Tagged: , | 2 Comments »

Calling ASP.NET Web Service (ASMX) from JQuery

Posted by coreycoogan on December 17, 2010


UPDATE: 12/17/2010 12:55 AM CST – Thanks to Dave’s comments below, I learned that the script service handles object serialization to JSON for me. This means that if your return type is that of an object, it will automatically serialize this object to JSON. If you want to create an anonymous object, such as you may do from a LINQ query, just set your return type to “object”. I thought I had to turn that into a string, but that’s not necessary. Thanks again Dave. I also removed the [ScriptMethod] attribute, which is isn’t necessary and actually causes the JSON returned to be a string and not a JSON object.

I’ve not done anything real meaningful in Web Forms in some time, so I was very surprised at how much trouble I had trying to call a simple HelloWorld service from a jQuery Post and have it return some JSON back to my screen.  It took a couple hours to finally get it right, and I know I’m not the only one.  There are Stack Overflow posts about this and many blog posts as well. Unfortunately, as helpful as those posts were, it seems that I was still missing things… the things I’m going to cover in this here post.

ASMX Web Service

First, let’s start with the old school web service that I’d like to use to communicate with the client.  Sure I could go WCF, but an ASMX is just fine for my needs and darn simple. The important things to note on the service:

  • To call a service from JavaScript, the service class must be decorated with the [ScriptService] attribute.
  • The service method should not decorated with the [ScriptMethod] attribute, only the [WebMethod] attribute. The ScriptMethod causes the return JSON to be a string that needs to be deserialized on the client. Without it, it comes down as a valid JSON object, automatically deserialized by jQuery for me.
  • If you are going to return a hardcoded JSON string for jQuery to parse, it has be formated with double quotes around the names and properties.  I was using single ticks, which I have gotten away with in some cases, but not here (return “{\”response\”:\”Hello World\”}”).
  • Mark the return type of the method to “object” if you want to return with an anonymous object from a LINQ query or something like that. 

This is an example of a working service:

    [ScriptService]
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class HelloWorld : System.Web.Services.WebService
    {
        [WebMethod]
        //[ScriptMethod(ResponseFormat = ResponseFormat.Json)] Don't need this attribute.  
        public HelloWorldResponse SayIt(string name)
        {
            //the Identity of the logged in user is available
            var userName = HttpContext.Current.User.Identity.Name;
            
           return new HelloWorldResponse() {Name = name, NickName = "Mad Dog"};
        }

        public class HelloWorldResponse
        {
            public string Name { get; set; }
            public string NickName { get; set; }
        }
    }

AJAX and jQuery

Now for the jQuery, for which I’m using version 1.4.4.  Here’s the things that tripped me up:

  • I was using a JQ $.post call in my web pages to communicate with the server.  That caused me some problems in that the data was always returned as XML, no matter what I tried.  Knowing that $.post is a shortcut against the $.ajax method, I switched to using that so I could explicitly set the contentType to “application/json”.  I do realize that I can probably set that on the $.post method, as I’m pretty sure I’ve done that in the past, but that can cause confusion, especially with new developers.
  • If you are accessing more than one service in more than place, create a utility method or use the $.ajaxSetup method to configure the defaults.
  • If you don’t set the dataType to “json”, the response data is returned as xml.
  • If you don’t pass empty data when there are no parameters needed for the service, the response data will be returned as xml.
  • The dictionary passed to the service in the data parameter must be quoted or the service will throw a 500 error.  Web Service parameters are passed with the name of the parameter as the key (see example).
  • This is discovered soon enough, but the response from the service comes wrapped in another JSON object, available at the ‘d’ key.  If the value for ‘d’ is a JSON string, it will have to be deserialized into a proper JSON object.  I use jQuery’s $.parseJSON function for that. This example will return ‘d’ as a proper JSON object.
<script>
    $(document).ready(function() {

        //this would be done in a common script file if you are going to
        //make a lot of these calls
        $.ajaxSetup({ type: 'POST', dataType: 'json', contentType: 'application/json', data: {} });

        $('#btn').click(function() { 
             //call the web service with the button is clicked
            $.ajax({ url: 'webservices/helloworld.asmx/SayIt',
                data: '{ "name": "Corey" }', //ensure the data is enclosed in a quote
                success: function(data) {
                    var responseJson = data.d; //the real json is wrapped, get it and parse it
                    $('#sayit').html(responseJson.Name + ' aka ' + responseJson.NickName); //put the response value from the return json
                }
            });

            //return false to avoid a postback
            return false;
        });

    });
</script>
<div>
	<button id='btn'>Load It</button>
	<div id='sayit'></div>
</div>

Authorization and Authentication

Any ASMX service written in our web project will be available to the world.  If you are using a ROLES and MEMBERSHIP provider and your site requires authentication, use the <location> tag in your web.config to lock down the directory where you put all your services.  In my example, I’ve put all my services in a WebServices directory and only authenticated users in the Admins role have access.

 <location path="WebServices">
    <system.web>
      <authorization>
        <deny users="?"/>
        <allow roles="Admins"/>
        <deny users="*"/>
      </authorization>
    </system.web>
  </location>

If the user is logged in, there will be an active IIdentity in HttpContext.Current.User.Identity, so your service can use that for authorization where necessary.

var userName = HttpContext.Current.User.Identity.Name;

Posted in Ajax, ASP.NET, jQuery | Tagged: , , , , , | 9 Comments »

JQuery’s Dialog and Form Problems

Posted by coreycoogan on December 1, 2010


Sometimes there is a need to put certain elements within a FORM in a JQuery Dialog.  This sounds simple enough, but unfortunately when the dialog is displayed, the form elements can get lost as they get added outside the Form’s DOM.

The solution is simple, append the dialog element to the form. Here’s an example of how it’s done.

<script>
$(document).ready(function() {

		
       //define the dialog for later use
        var dlg = $('#AddressVerification').dialog(
        {
            autoOpen: false,
            closeOnEscape: false,
            modal: true,
            width: 550 
           }
        });

        //This is where we tie the dialog content to the parent form
       $("#AddressVerification").parent().appendTo($("form:first"));
		
		//other code not pertinent to this
});
</script>
<form>
<!-- form elements -->

<div id="AddressVerification" style="display: none;">
        <!-- address form elements -->
        <p>
            <button id="btnSave">Save</button>
            <button id="btnCancel">Cancel</button>
        </p>
    </div>

</form>

Posted in jQuery, UI | 2 Comments »

Adding Icons to ValidationSummary and Information Boxes with JQuery

Posted by coreycoogan on November 2, 2009


What’s so special about ValidationSumary and Information Boxes?

When there are errors or important information that you need to call out to your users, it is important to make it unmistakably noticeable. You want to stack the odds in your favor as much as possible that the user will see your call outs and act on them accordingly. Unfortunately, the ValidationSummary in ASP.NET and ASP.NET MVC comes very vanilla. You can change the appearance in MVC by defining the “validation-summary-errors” CSS class that is set to the ValidationSummary by default, but this isn’t always enough. The same goes true for an Information Box, which is what I call a box that contains important information that I want the user to see.

It is very typical for users to gloss over these important messages when they are rendered as text only. Placing the text in a colored box with a brightly colored border can help, but I’ve still experienced frustrated users claiming that registration is broken because they miss the message that says their email has already been used. To combat this, it’s a good practice to use familiar operating system icons for Error, Information, Warning, etc. Most users will recognize familiar icons and have learned to pay attention when they see them.

Here’s an example of what we’re going to achieve:

Error callout box

Info Callout Box

Styling the “Callout Box”

I use the same technique to style all my callout boxes. It’s very simple to do with JQuery, CSS and a few icons. I’ll demonstrate this for both an Error and Info callout.

First, the CSS classes. Each type of callout (error, info, warning) will require 2 classes. One class will style the box that contains the text and the other will be used to hold the applicable icon.

.infobox
{
    border: solid 1px #228ef1; 
    padding: 5px 8px 5px 8px;
    text-align:left;
    vertical-align:middle;
    background-color:#F1E7D1;
}

.infoimage
{
   padding: 5px 15px 5px 15px;
    background:#F1E7D1 url(/images/icons/info-24x24.png) no-repeat;
    background-position:left center;
}

.errorbox
{
    border: solid 1px red; 
    padding: 5px 8px 5px 8px;
    text-align:left;
    vertical-align:middle;
    background-color:#F1E7D1;
}

.errorimage
{
   padding: 5px 15px 5px 15px;
    background:#F1E7D1 url(/images/icons/err-24x24.png) no-repeat;
    background-position:left center;
    vertical-align:middle;
    display:inline-block;
    height:100%;
}

For my error callouts, I want to add one more class. This will handle the heading of my errors, which typically reads something like “Please fix the following errors”.

.errorhead
{
    color:#CC0000;
    font-weight:bold;
    font-size:15px;
    padding-left:18px;
}

Using JQuery to Construct the Callout Box

The HTML used to put the icon in the callout box requires nested DIV or SPAN elements. I don’t want to depend on my memory to always add the nested elements each time I want a callout, I want to simply write something like this:

<div id="CustomSearchSelect" class="infobox">
            <strong>Can't Find Your School?</strong> Try a <a id="CustomSearchLink" href="#">Custom Search</a>.
</div>

<div class='errorbox'>
This action is not allowed!
</div>

That’s where JQuery is able to help us. By using the prepend function, we can have the appropriate icon automatically injected into our callout box. We do this by adding the following to our JQuery ready function in our Master Page.

$(document).ready(function() {
$(".infobox").prepend("<span class='infoimage'>&nbsp;</span>");
$(".errorbox").prepend("<span class='errorimage'><span class='errorhead'>Looks like we have a small problem...</span></span>");
});

Pretty simple, right? Here’s what’s happening here. The script is finding the elements styled with the infobox and errorbox class and sticking in a SPAN element styled with the corresponding icon class. The errorbox gets the additional heading added here as well. You could do the same thing for Warn, Question or any other conventions you wish to follow.

Styling the ValidationSummary

The ValidationSummary requires a little bit of extra effort. This is because I’m using the xVal 1.0 and a ValidationSummary for client-side error handling as described in my last blog post. The only thing I need to do is define the HTML for my ValidationSummary, but hide it unless I have Model Errors in my MVC application. This is done by wrapping my ValidationSummary in a div styled with the errorbox class like so.

<% string display = ViewData.ModelState.IsValid ? "none" : "block"; %>
<div class="errorbox" id="validationSummary" style="display:<%=display%>"> 
    <%= Html.ValidationSummary() %>
</div>

Finding the Icons

I found some pretty nice icons on various free icon websites. It took a bit of time to run them all down, so I’m including them here for you in a zip file.

Icon Zip File

Posted in ASP.NET, ASP.NET MVC, CSS, jQuery, UI | Tagged: , , , , | 4 Comments »

Client Side Dependent Dropdowns with JQuery without AJAX

Posted by coreycoogan on September 12, 2009


There are several terms to describe the common UI technique of having 2 dropdown lists, with the selections of the first dropdown changing the options of the second.  Common terms for this include Dependent Dropdowns, Context Sensitive Dropdowns,  Contextual Dropdowns and probably others.

I recently had a requirement for this functionality in a control in an ASP.NET MVC project.  When I searched for some samples on how to do this in JQuery, I realized that all the samples I found used AJAX to populate the second list.  In this particular case, it made more sense to keep everything on the client, so that’s what I developed.  This post is about how I accomplished this using JQuery, JSON.NET and a ViewModel with a couple extension methods.  If you’re looking for ways to do this with AJAX, you’ll have to read one of the many other examples in the blogosphere.

The Requirement

The user needs a way to choose a color for a product.  The chosen color is categorized under a parent color (Blue.Baby, Blue.Sky, Yellow.Banana, Yellow.Lemon).  The user should be able to first pick a parent color and then view a list of all child colors from which to pick.  The catalog of color selections is stored in a database and should be used to drive the dropdowns.  The dropdowns are just a helper as the user is able to input any color name they wish, regardless of whether or not it appears in the second dropdown.  Up to 5 colors can be chosen per product, so the color selection should be a control that can be repeated on a view, however, this post will ignore that requirement and show how this can be accomplished in a singular fashion.

The  Object Model

The ViewModel that drives the View where this functionality resides contains an IList<VendorColor> property, which will be used to populate the dropdowns.  The VendorColor class looks like this:


public class VendorColor
 {
      readonly int _vendorColorId;
      readonly string _primaryColor;
      readonly string _secondaryColor;

 public VendorColor(int vendorColorId, string primaryColor,
                                       string secondaryColor)
 {
      _vendorColorId = vendorColorId;
      _primaryColor = primaryColor;
      _secondaryColor = secondaryColor;
 }

 public int VendorColorId { get { return _vendorColorId; } }

 public string PrimaryColor { get { return _primaryColor; } }

 public string SecondaryColor { get { return _secondaryColor; } }
 }

Extension Methods

In order to keep everything on the client, we’ll need to get the pertinent data from the collection of VendorColor objects to the View so it’s accessible by JQuery and JavaScript.  To do this, 2 extension methods were written to serialize the required data to JavaScript objects.

First, we need to gather the distinct PrimaryColor values from the list of VendorColor objects and get them into an array.  Here’s what that extension method looks like.


public static string PrimaryColorsAsJsArray(this List colors)
 {
 //get the distinct primary colors from the collection
 var primaries =
 (from c in colors
 select c.PrimaryColor).Distinct();

 //append each value into a JavaScript Array string, start with an empty string for blank option
 StringBuilder sb = new StringBuilder("var arrPrimary = [\"\",");
 foreach (var primary in primaries)
 sb.AppendFormat("\"{0}\",", primary);

 //remove the last comma and add ending bracket
 sb.Remove(sb.Length - 1, 1);
 sb.Append("];");

 return sb.ToString();

 }

This results in the following javascript:

var arrPrimary = ["","Red","Blue","Green","Yellow"];

The next extension method will be used to serialize the entire collection of VendorColor objects to JSON objects that we can later parse with JQuery and fill our dependent dropdown list. This is ridiculously easy with the help of the awesome JSON.NET library. Check out what can be done in one line of code!


public static string VendorColorsToJsonArrary(this List colors)
 {
 return "var arrSecondary = " +
 Newtonsoft.Json.JsonConvert.SerializeObject(colors) + ";";
 }

This results in the following javascript (formatted for readability):

var arrSecondary = [
                    {"VendorColorId":1,"PrimaryColor":"Red","SecondaryColor":"blood"},
                    {"VendorColorId":2,"PrimaryColor":"Red","SecondaryColor":"apple"},
                    {"VendorColorId":3,"PrimaryColor":"Blue","SecondaryColor":"sky"},
                    {"VendorColorId":4,"PrimaryColor":"Blue","SecondaryColor":"baby"},
                    {"VendorColorId":5,"PrimaryColor":"Green","SecondaryColor":"lime"},
                    {"VendorColorId":6,"PrimaryColor":"Green","SecondaryColor":"lettuce"},
                    {"VendorColorId":7,"PrimaryColor":"Yellow","SecondaryColor":"sunshine"},
                    {"VendorColorId":8,"PrimaryColor":"Yellow","SecondaryColor":"lemon"}
                 ];

The View

Now let’s tie it all together by seeing the view and all the javascript that makes this work. It should be noted that my view is utilizing the latest version of the JQuery library.


    //use vars for my controls so JQuery and Javascript won't have to retype each time
    var primaryDd = '#PrimaryColor';
    var secondaryDd = '#SecondaryColor';

 

Now the script that makes all this work, complete with comments. In the real world, this is stored in an external .js file, as it should be. This keeps the views cleaner and Google favors it for SEO. Keep in mind that the script is using the vars defined above.

function ClearDd1(dropDown1) {
    $(dropDown1)[0].options.length = 0;
}

function ClearDd2(dropDown2) {
    $(dropDown2)[0].options.length = 0;
}

function BindSecondary(dd1, dd2) {

    //clear the options before rebinding them
    ClearDd2(dd2);

    //get the selected value from dropdown 1
    var sel = $(dd1).val();

    //use the JQuery grep function to find all objects in my JSON collection
    //where the PrimaryColor equals the selection from the first dropdown.
    //this is really the key to making this work elegantly
    var arr = $.grep(arrSecondary, function(a) { return a.PrimaryColor == sel; });

    //for each JSON object, write an option to the second dropdown
    $(arr).each(
                function(i) {
                    $("").attr("value", arr[i].VendorColorId)
                                      .text(arr[i].SecondaryColor).appendTo(dd2);
                });
}

function SelectColor(dd2, hid, txt) {

    //the selected value from the second dropdown
    var selVal = $(dd2).val();

    //make sure a color has been selected
    if (selVal == '') {
        alert('You must select a secondary color or cancel');
        return false;
    }

    //grab the text from the selected option
    var selText = $(dd2 + ' :selected').text();

    //store the ID, or option value, in a hidden form field
    $(hid).val(selVal);
    
    //send the selected color into a text field
    $(txt).val(selText);
}

Conclusion

JQuery and ASP.NET Ajax has made it very easy to use AJAX to accomplish dependent dropdowns. In many cases, it’s wasteful and unnecessary to go to the server for this information over and over again. In such cases it can be advantageous to do this purely on the client. Using JSON.NET to serialize objects from .NET to JSON and using JQuery and its “grep()”, “each()” and “val()” methods to get the right data and handle population and selection. This solution didn’t take too long to develop, but if you know a better way, I’d love to hear about it.

Posted in ASP.NET MVC, jQuery | Tagged: , , , | 1 Comment »