Active Directory in C#.Net


Microsoft Technet offers a script repository to work with AD User Accounts; however, I needed to work with .NET and I could not find samples for all the tasks I needed to program. I promised to myself that one day I would publish the code samples I found and created to help other developers who are working with Directory Services. So, I wish you a happy AD.NET programming and I hope my work saves you some time. The code samples I provide are written in C#.
1. Create a connection to Active Directory
/// <summary>/// Method used to create an entry to the AD./// Replace the path, username, and password./// </summary>/// <returns>DirectoryEntry</returns>public static DirectoryEntry GetDirectoryEntry()
{
DirectoryEntry de =
new
DirectoryEntry();
de.Path = LDAP://192.168.1.1/CN=Users;DC=Yourdomain;
de.Username = @"yourdomain\sampleuser";
de.Password = "samplepassword";
return
de;
}
2. Create a secure connection to Active Directory
To connect to the AD, you need a user account that belongs to the domain you want to connect to. Most user accounts have permissions to search the AD; however, to modify the AD, you need a user account that is a member of the group of Domain Administrators (DomainAdmin). An account that belongs to this group has high privileges and hardcoding the user and password of this account in your code can compromise the security of the AD. I don't recommend you to create directory entries where usernames and passwords are hardcoded. Try to connect to the AD using a secure connection.
/// <summary>/// Method used to create an entry to the AD using a secure connection./// Replace the path./// </summary>/// <returns>DirectoryEntry</returns>public static DirectoryEntry GetDirectoryEntry()
{
DirectoryEntry de =
new
DirectoryEntry();
de.Path = LDAP://192.168.1.1/CN=Users;DC=Yourdomain;
de.AuthenticationType = AuthenticationTypes.Secure;
return
de;
}

To connect to the AD using a secure connection, you need to delegate the permissions of a user account with DomainAdmin permissions to the thread that is running a program. For instance, I created an exe and I ran the program using the Run As command to start a program. I delegated the user's principal identity and culture to the current thread that runs the program. To delegate the principal identity and culture to the current thread, I used the following code:
/// <summary>/// Establish identity (principal) and culture for a thread./// </summary>public static void SetCultureAndIdentity()
{
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
WindowsPrincipal principal = (WindowsPrincipal)Thread.CurrentPrincipal;
WindowsIdentity identity = (WindowsIdentity)principal.Identity;
System.Threading.Thread.CurrentThread.CurrentCulture =
new
CultureInfo("en-US");
}

3. Validate if a user exists
/// <summary>/// Method to validate if a user exists in the AD./// </summary>/// <param name="UserName"></param>/// <returns></returns>public bool UserExists(string UserName)
{
DirectoryEntry de = ADHelper.GetDirectoryEntry();
DirectorySearcher deSearch =
new
DirectorySearcher();
deSearch.SearchRoot =de;
deSearch.Filter = "(&(objectClass=user) (cn=" + UserName +"))";
SearchResultCollection results = deSearch.FindAll();
if
(results.Count == 0)
{
return false
;
}
else{return true;
}
}

4. Set user's properties
/// <summary>/// Helper method that sets properties for AD users./// </summary>/// <param name="de"></param>/// <param name="PropertyName"></param>/// <param name="PropertyValue"></param>public static void SetProperty(DirectoryEntry de, string PropertyName, string PropertyValue)
{
if(PropertyValue!=null
)
{
if
(de.Properties.Contains(PropertyName))
{
de.Properties[PropertyName][0]=PropertyValue;
}
else{
de.Properties[PropertyName].Add(PropertyValue);
}
}

5. Set user's country
To set the country property for a user was one of the tasks that took me some time to figure out. After some hours of research I realized that you need to know the ISO 3166 Codes for countries and set three properties to define a user's country: c, co, and countryCode.
// Set the co property using the name of the country.SetProperty(newuser,"co","MEXICO");// Set the c property using the two-letter country code (ISO 3166 A 2).SetProperty(newuser,"c","MX");// Set the countryCode property using the numeric value (ISO 3166 Number) of the country.SetProperty(newuser,"countryCode","484");}
6. Set user's password
Setting the password for a user requires some work. I will walk you through the steps I followed to set a password for a user:
a) Create or download a helper class that generates random passwords that comply with the strong password rules. I was short of time and couldn't develop one, so I downloaded the RandomPassword class created by Obviex.b) Create a method that consumes the RandomPassword helper class
/// <summary>/// Method that consumes a helper class library/// to generate random passwords./// </summary>/// <returns></returns>public string SetSecurePassword()
{
RandomPassword rp =
new
RandomPassword();return rp.Generate(8,8);
}
c) Set the password property using the usr.Invoke method.
/// <summary>/// Method to set a user's password/// <param name="path"></param>public void SetPassword(string path)
{
DirectoryEntry usr =
new
DirectoryEntry();
usr.Path = path;
usr.AuthenticationType = AuthenticationTypes.Secure;
object[] password = new object
[] {SetSecurePassword()};object ret = usr.Invoke("SetPassword", password );
usr.CommitChanges();
usr.Close();
}
The usr.Invoke method can be called once within the same AppDomain, otherwise your program will crash. If you place a call to the usr.Invoke method inside a for construct, the first run will be succesful, but the second one will crash the compiler. I created a workaround that helped me to solve this problem. I made a separate console application (SetPassword.exe) and I called and started the process programatically from the SetPassword method.

  • Download the SetPassword project.
  • Copy the SetPassword.exe file in your application.
  • Call and start SetPassword.exe from your application.
/// </summary>/// Method that calls and starts SetPassword.exe/// <param name="path"></param>/// <param name="password"></param>public void SetPassword(string path, string password)
{
StringBuilder args =
new
StringBuilder();
args.Append(path);
args.Append(" ");
args.Append(password);
ProcessStartInfo startInfo =
new
ProcessStartInfo("SetPassword.exe",args.ToString());
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(startInfo);
}
7. Enable a user account
/// <summary>/// Method to enable a user account in the AD./// </summary>/// <param name="de"></param>private static void EnableAccount(DirectoryEntry de)
{
//UF_DONT_EXPIRE_PASSWD 0x10000int exp = (int) de.Properties["userAccountControl"].Value;
de.Properties["userAccountControl"].Value = exp | 0x0001;
de.CommitChanges();//UF_ACCOUNTDISABLE 0x0002int val = (int) de.Properties["userAccountControl"].Value;
de.Properties["userAccountControl"].Value = val & ~0x0002;
de.CommitChanges();
}
8. Add a user to a group
/// <summary>/// Method to add a user to a group/// </summary>/// <param name="de"></param>/// <param name="deUser"></param>/// <param name="GroupName"></param>public static void AddUserToGroup(DirectoryEntry de, DirectoryEntry deUser, string GroupName)
{
DirectorySearcher deSearch =
new
DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(&(objectClass=group) (cn=" + GroupName +"))";
SearchResultCollection results = deSearch.FindAll();
bool isGroupMember = false
;if (results.Count>0)
{
DirectoryEntry group = GetDirectoryEntry(results[0].Path);
object members = group.Invoke("Members",null
);foreach ( object member in (IEnumerable) members)
{
DirectoryEntry x =
new
DirectoryEntry(member);if (x.Name! = deUser.Name)
{
isGroupMember =
false
;
}
else{
isGroupMember =
true
;break;
}
}
if
(!isGroupMember)
{
group.Invoke("Add",
new object
[] {deUser.Path.ToString()});
}
group.Close();
}
return
;
}
9. Generate a mailbox for a user in Microsoft Exchange Server
You might need to create a mailbox for a user in Microsoft Exchange Server. Network configuration and server architecture can add complexity to the process of programmatically creating mailboxes for users, but you know, there's always a workaround. You can invoke a script that creates mailboxes from a remote machine. I will walk you through the steps I followed to create a mailbox for a user in Microsoft Exchange Server.

  • On the Domain Controller server, create the directory C:\TestRemoteMailbox.
  • Download the scripts MailBox.vbs and WSHControl.vbs.
  • Copy the script Mailbox.vbs to the directory C:\TestRemoteMailbox.
  • Note: Mailbox.vbs is a script that creates MailBoxes in Microsoft Exchange.
  • Copy the script WSHControl.vbs to your application directory.
  • Note: WSHControl.vbs is a script that invokes the MailBox.vbs script on a remote machine (Domain Controller)
  • From your application, call and start WSHControl.vbs.

/// <summary>/// Method that calls and starts a WSHControl.vbs/// </summary>/// <param name="userAlias"></param>public void GenerateMailBox(string userAlias)
{
StringBuilder mailargs =
new
StringBuilder();
mailargs.Append("WSHControl.vbs");
mailargs.Append(" ");
mailargs.Append(userAlias);
ProcessStartInfo sInfo =
new
ProcessStartInfo("Wscript.exe",mailargs.ToString());
sInfo.WindowStyle = ProcessWindowStyle.Hidden;;
Process.Start(sInfo);
}
10. Create a user account
/// <summary>/// Method that creates a new user account/// </summary>/// <param name="employeeID"></param>/// <param name="name"></param>/// <param name="login"></param>/// <param name="email"></param>/// <param name="group"></param>public void CreateNewUser(string employeeID, string name, string login, string email, string group)
{
Catalog catalog =
new
Catalog();
DirectoryEntry de = ADHelper.GetDirectoryEntry();
///
1. Create user accountDirectoryEntries users = de.Children;
DirectoryEntry newuser = users.Add("CN=" + login, "user");
///
2. Set propertiesSetProperty(newuser,"employeeID", employeeID);
SetProperty(newuser,"givenname", name);
SetProperty(newuser,"SAMAccountName", login);
SetProperty(newuser,"userPrincipalName", login);
SetProperty(newuser,"mail", email);
newuser.CommitChanges();
///
3. Set passwordSetPassword(newuser.Path);
newuser.CommitChanges();
///
4. Enable account EnableAccount(newuser);/// 5. Add user account to groupsAddUserToGroup(de,newuser,group);/// 6. Create a mailbox in Microsoft Exchange GenerateMailBox(login);
newuser.Close();
de.Close();
}
11. Disable a user account
/// <summary>/// Method that disables a user account in the AD and hides user's email from Exchange address lists./// </summary>/// <param name="EmployeeID"></param>public void DisableAccount(string EmployeeID)
{
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher ds =
new
DirectorySearcher(de);
ds.Filter = "(&(objectCategory=Person)(objectClass=user)(employeeID=" + EmployeeID + "))";
ds.SearchScope = SearchScope.Subtree;
SearchResult results = ds.FindOne();
if(results != null
)
{
DirectoryEntry dey = GetDirectoryEntry(results.Path);
int val = (int
)dey.Properties["userAccountControl"].Value;
dey.Properties["userAccountControl"].Value = val | 0x0002;
dey.Properties["msExchHideFromAddressLists"].Value = "TRUE";
dey.CommitChanges();
dey.Close();
}
de.Close();
}
12. Update user account
/// <summary>/// Method that updates user's properties/// </summary>/// <param name="employeeID"></param>/// <param name="department"></param>/// <param name="title"></param>/// <param name="company"></param>public void ModifyUser(string employeeID, string department, string title, string company)
{
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher ds =
new
DirectorySearcher(de);
ds.Filter = "(&(objectCategory=Person)(objectClass=user)(employeeID=" + employeeID + "))";
ds.SearchScope = SearchScope.Subtree;
SearchResult results = ds.FindOne();
if(results!=null
)
{
DirectoryEntry dey = GetDirectoryEntry(results.Path);
SetProperty(dey, "department", department);
SetProperty(dey, "title", title);
SetProperty(dey, "company", company);
dey.CommitChanges();
dey.Close();
}
de.Close();
}
13. Validate if a string has a correct email pattern.
/// <summary>/// Method that validates if a string has an email pattern./// </summary>/// <param name="mail"></param>/// <returns></returns>public bool IsEmail(string mail)
{
Regex mailPattern =
new
Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*");return mailPattern.IsMatch(mail);
}
14. Extract a user alias from an email account.
/// <summary>/// Method to extract the alias from an email account./// dada una cuenta de correo electrónico/// </summary>/// <param name="mailAddress"></param>/// <returns></returns>public string GetAlias(string mailAddress)
{
if
(IsEmail(mailAddress))
{
return
mailAddress.Substring(0,mailAddress.IndexOf("@"));
}
else{return "";
}
}
15. Format dates to AD date format (AAAAMMDDMMSSSS.0Z)
/// <summary>/// Method that formats a date in the required format/// needed (AAAAMMDDMMSSSS.0Z) to compare dates in AD./// </summary>/// <param name="date"></param>/// <returns>Date in valid format for AD</returns>public string ToADDateString(DateTime date)
{
string
year = date.Year.ToString();int month = date.Month;int day = date.Day;
StringBuilder sb =
new
StringBuilder();
sb.Append(year);
if
(month <10)
{
sb.Append("0");
}
sb.Append(month.ToString());
if
(day <10)
{
sb.Append("0");
}
sb.Append(day.ToString());
sb.Append("000000.0Z");
return
sb.ToString();
}
16. Search users
When you use Directory Services, you can accomplish many interesting tasks such as searching and filtering users. The DirectorySearcher object allows you to query the AD. The following sample code queries the AD to search all the user accounts that were modified from a given date. The results are stored in a DataTable, so you can easily databind them.
/// <summary>/// Method that returns a DataTable with a list of users modified from a given date./// </summary>/// <param name="fromdate"></param>public DataTable GetModifiedUsers(DateTime fromdate)
{
DataTable dt =
new
DataTable();
dt.Columns.Add("EmployeeID");
dt.Columns.Add("Name");
dt.Columns.Add("Email");
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher ds =
new
DirectorySearcher(de);
StringBuilder filter =
new
StringBuilder();
filter.Append("(&(objectCategory=Person)(objectClass=user)(whenChanged>=");
filter.Append(date.ToADDateString());
filter.Append("))");
ds.Filter=filter.ToString();
ds.SearchScope = SearchScope.Subtree;
SearchResultCollection results= ds.FindAll();
foreach(SearchResult result in
results)
{
DataRow dr = dt.NewRow();
DirectoryEntry dey = GetDirectoryEntry(result.Path);
dr["EmployeeID"] = dey.Properties["employeeID"].Value;
dr["Name"] = dey.Properties["givenname"].Value;
dr["Email"] = dey.Properties["mail"].Value;
dt.Rows.Add(dr);
dey.Close();
}
de.Close();
return
dt;

jQuery 1.4 New Features you Must Know

jQuery 1.4 was recently released. This wasn't simply a maintenance release as some had speculated; there are many new features, enhancements and performance improvements included in 1.4! This post covers the new features and enhancements that you may find beneficial.
You can download jQuery 1.4 right now, here: http://code.jquery.com/jquery-1.4.js

Pre 1.4, jQuery supported adding attributes to an element collection via the useful "attr" method, which can be passed both an attribute name and value, or an object specifying several attributes. jQuery 1.4 adds support for passing an attributes object as the second argument to the jQuery function itself, upon element creation.
Let's say you need to create an anchor element with several attributes. With 1.4 it's as simple as:
1
2
3
4
5
6
7
jQuery('<a/>', {
    id: 'foo',
    href: 'http://google.com',
    title: 'Become a Googler',
    rel: 'external',
    text: 'Go to Google!'
});
You may have noticed the "text" attribute— you'll probably be wondering what that's doing there, after all there's no "text" attribute for anchors! Well, jQuery 1.4 utilises its very own methods when you pass certain attributes. So the "text" attribute specified above would cause jQuery to call the ".text()" method, passing "Go to Google!" as its only argument.
A better example of this in action:
01
02
03
04
05
06
07
08
09
10
jQuery('<div/>', {
    id: 'foo',
    css: {
        fontWeight: 700,
        color: 'green'
    },
    click: function(){
        alert('Foo has been clicked!');
    }
});
The "id" is added as a regular attribute, but the "css" and "click" properties trigger calling of each respective method. The above code, before the 1.4 release, would have been written like this:
1
2
3
4
5
6
7
8
9
jQuery('<div/>')
    .attr('id', 'foo')
    .css({
        fontWeight: 700,
        color: 'green'
    })
    .click(function(){
        alert('Foo has been clicked!');
    });

Three new methods have been added to the DOM traversal arsenal in 1.4, "nextUntil", "prevUntil" and "parentsUntil". Each of these methods will traverse the DOM in a certain direction until the passed selector is satisfied. So, let's say you have a list of fruit:
1
2
3
4
5
6
7
8
9
<ul>
    <li>Apple</li>
    <li>Banana</li>
    <li>Grape</li>
 
    <li>Strawberry</li>
    <li>Pear</li>
    <li>Peach</li>
</ul>
You want to select all of items after "Apple", but you want to stop once you reach "Strawberry". It couldn't be simpler:
1
2
jQuery('ul li:contains(Apple)').nextUntil(':contains(Pear)');
// Selects Banana, Grape, Strawberry

Instead of chaining a bunch of event binding methods together, you can lump them all into the same call, like so:
01
02
03
04
05
06
07
08
09
10
11
jQuery('#foo).bind({
    click: function() {
        // do something
    },
    mouseover: function() {
        // do something
    },
    mouseout: function() {
        // do something
    }
})
This also works with ".one()".

Instead of just defining one easing function for a single animation, you can now define a different easing function for each property that you're animating. jQuery includes two easing functions, swing (the default) and linear. For other ones you'll need to download them separately!
To specify an easing function for each property simply define the property as an array, with the first value being what you want to animate that property to, and the second being the easing function to use:
1
2
3
4
jQuery('#foo').animate({
    left: 500,
    top: [500, 'easeOutBounce']
}, 2000);

You can also define per-property easing functions in the optional options object as property name-value pairs in the "specialEasing" object:
1
2
3
4
5
6
7
8
9
jQuery('#foo').animate({
    left: 500,
    top: 500
}, {
    duration: 2000,
    specialEasing: {
        top: 'easeOutBounce'
    }
});
Editor's Note - The author of this article, James Padolsey, is being modest. This new feature was his idea!

jQuery 1.4 adds support for delegating the "submit", "change", "focus" and "blur" events. In jQuery, we use the ".live()" method to delegate events. This is useful when you have to register event handlers on many elements, and when new elements may be added over time (using ".live()" is less-costly than re-binding continually).
But, be careful! You must use the event names, "focusin" and "focusout" if you want to delegate the "focus" and "blur" events!
1
2
3
jQuery('input').live('focusin', function(){
    // do something with this
});
jQuery 1.4 provides a new "proxy" function under the jQuery namespace. This function takes two arguments, either a "scope" and a method name, or a function and the intended scope. JavaScript's "this" keyword can be quite tricky to keep a hold of. Sometimes you won't want it to be an element, but instead an object that you've previously created.
For example, here we've got an "app" object which has two properties, a "clickHandler" method and a config object:
1
2
3
4
5
6
7
8
var app = {
    config: {
        clickMessage: 'Hi!'
    },
    clickHandler: function() {
        alert(this.config.clickMessage);
    }
};
The "clickHandler" method, when called like "app.clickHandler()" will have "app" as its context, meaning that the "this" keyword will allow it access to "app". This works quite well if we simply call:
1
app.clickHandler(); // "Hi!" is alerted
Let's try binding it as an event handler:
1
jQuery('a').bind('click', app.clickHandler);
When we click an anchor it doesn't appear to work (nothing is alerted). That's because jQuery (and most sane event models) will, by default, set the context of the handler as the target element,- that is, the element that's just been clicked will be accessible via "this". But we don't want that, we want "this" to be set to "app". Achieving this in jQuery 1.4 couldn't be simpler:
1
2
3
4
jQuery('a').bind(
    'click',
    jQuery.proxy(app, 'clickHandler')
);
Now whenever an anchor is clicked, "Hi!" will be alerted!
The proxy function returns a "wrapped" version of your function, with "this" set to whatever you specify. It's useful in other contexts too, such as passing callbacks to other jQuery methods, or to plugins.

You can now add a delay to your animation queues. In fact, this works on any queue, but its most common use case will probably be with the "fx" queue. This allows you to pause between animations without having to mess with callbacks and calls to "setTimeout". The first argument to ".delay()" is the amount of milliseconds you want to delay for.
1
2
3
4
jQuery('#foo')
    .slideDown() // Slide down
    .delay(200) // Do nothing for 200 ms
    .fadeIn(); // Fade in
If you want to delay a queue other than the default "fx" queue, then you'll need to pass the queue name as the second argument to ".delay()".

jQuery 1.4 makes it easy to check if an element (or collection) ".has()" something. This is the programmatic equivalent to jQuery's selector filter, ":has()". This method will select all elements in the current collection that contain at least one element that complies with the passed selector.
1
jQuery('div').has('ul');
That would select all DIV elements that contain UL elements. In this situation you'd probably just use the selector filter (":has()"), but this method is still useful when you need to filter a collection programmatically.
jQuery 1.4 also reveals the "contains" function under the jQuery namespace. This is a low-level function that accepts two DOM nodes. It'll return a boolean indicating whether the second element is contained within the first element. E.g.
1
2
jQuery.contains(document.documentElement, document.body);
// Returns true - <body> is within <html>

We've had the ".wrap()" method for a while now. jQuery 1.4 adds the ".unwrap()" method which does the complete opposite. If we assume the following DOM structure:
1
2
3
<div>
    <p>Foo</p>
</div>
We can unwrap the paragraph element like so:
1
jQuery('p').unwrap();
The resulting DOM structure would be:
1
<p>Foo</p>
Essentially, this method simply removes the parent of any element.

The new ".detach()" method allows you to remove elements from the DOM, much like the ".remove()" method. The key difference with this new method is that it doesn't destroy the data held by jQuery on that element. This includes data added via ".data()" and any event handlers added via jQuery's event system.
This can be useful when you need to remove an element from the DOM, but you know you'll need to add it back at a later stage. Its event handlers and any other data will persist.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
var foo = jQuery('#foo');
 
// Bind an important event handler
foo.click(function(){
    alert('Foo!');
});
 
foo.detach(); // Remove it from the DOM
 
// … do stuff
 
foo.appendTo('body'); // Add it back to the DOM
 
foo.click(); // alerts "Foo!"

jQuery 1.4 gives you two new ways to use the ".index()" method. Previously, you could only pass an element as its argument and you'd expect a number to be returned indicating the index of that element within the current collection.
Passing no arguments now returns the index of an element amongst its siblings. So, assuming the following DOM structure:
1
2
3
4
5
6
7
8
9
<ul>
    <li>Apple</li>
    <li>Banana</li>
    <li>Grape</li>
 
    <li>Strawberry</li>
    <li>Pear</li>
    <li>Peach</li>
</ul>
When a list item is clicked you want to find out the index of the clicked element amongst all the other list items. It's as simple as:
1
2
3
jQuery('li').click(function(){
    alert( jQuery(this).index() );
});
jQuery 1.4 also allows you to specify a selector as the first argument to ".index()", doing so will give you the index of the current element amongst the collection produced from that selector.
You should note that what's returned from this method is an integer, and it will return -1 if the selector/element passed cannot be found in the document.

Most of the DOM manipulation methods now support passing a function as the sole argument (or second, in the case of ".css()" & ".attr()"). This function will be run on every element in the collection to determine what should be used as the real value for that method.
The following methods have this capability:
  • after
  • before
  • append
  • prepend
  • addClass
  • toggleClass
  • removeClass
  • wrap
  • wrapAll
  • wrapInner
  • val
  • text
  • replaceWith
  • css
  • attr
  • html
Within the callback function, you'll have access to the current element in the collection via "this" and its index via the first argument.
1
2
3
jQuery('li').html(function(i){
    return 'Index of this list item: ' + i;
});
Also, with some of the above methods you'll also get a second argument. If you're calling a setter method (like ".html()" or ".attr('href)") you'll have access to the current value. E.g.
1
2
3
jQuery('a').attr('href', function(i, currentHref){
    return currentHref + '?foo=bar';
});
As you can see, with the ".css()" and ".attr()" methods, you would pass the function as the second argument, since the first would be used to name the property you wish to change:
1
2
3
jQuery('li').css('color', function(i, currentCssColor){
    return i % 2 ? 'red' : 'blue';
});
jQuery 1.4 adds two new helper functions (stored directly under the jQuery namespace) that help you determine what type of object you're dealing with.
First, there's "isEmptyObject", this function returns a boolean indicating whether or not the the passed object is empty (devoid of properties - direct and inherited). Second, there's "isPlainObject", which will return a boolean indicating whether the passed object is a plain JavaScript object, that is, one created via "{}" or "new Object()".
1
2
3
4
5
6
jQuery.isEmptyObject({}); // true
jQuery.isEmptyObject({foo:1}); // false
 
jQuery.isPlainObject({}); // true
jQuery.isPlainObject(window); // false
jQuery.isPlainObject(jQuery()); // false
Read more about: isPlainObject(…), isEmptyObject(…)
jQuery's ".closest()" method now accepts an array of selectors. This is useful when you want to traverse the ancestors of an element, looking for (more than one) closest elements with certain characteristics.
In addition, it now accepts a context as the second argument, meaning that you can control just how far or how close you want the DOM traversed to. Both of these enhancements accommodate rare use cases but they are used internally to great effect!
Read more about .closest(…)
As mentioned, to delegate the "focus" and "blur" events you must use these new events, called "focusin" and "focusout". These events allow you to take action when an element, or a descendant of an element, gains focus.
1
2
3
4
5
6
7
jQuery('form')
    .focusin(function(){
        jQuery(this).addClass('focused');
    });
    .focusout(function(){
        jQuery(this).removeClass('focused');
    });

You should also note that both of these events do not propagate ("bubble"); they are captured. This means that the outermost (ancestor) element will be triggered before the causal "target" element.