LINQ-to-SQL Sugar

December 21st, 2009 — 8:33pm

Yes, yes, LINQ-to-SQL is probably, one day, going to be consumed or deprecated by it’s big brother the Entity Framework. However, for the current .NET platform (3.5) and likely for the next (4.0) it is still a viable ORM option in the Visual Studio box. There are lots of reasons to use LINQ-to-SQL and lots of reasons not to use it…that is for another post.

What I do want to share is a set of extension methods that (for me) made using LINQ-to-SQL just a little bit nicer. The title is indeed descriptive…these are “sugar” added to LINQ-to-SQL and don’t fundamentally improve how it works, just how the programmer works with the library. I’m sure developers using LINQ-to-SQL have created scads of other such saccarine extensions to the framework…if you have some I’d be interested in hearing about them.

So here’s my LinqToSqlExtensions class:

public static class LinqToSqlExtensions {
 
    public static TEntity GetById<TEntity>(this Table<TEntity> @this, int id)
        where TEntity : class {
        return @this.SingleOrDefault(x => ((IIdentified)x).ID == id);
    }
 
    public static TEntity Upsert<TEntity>(this Table<TEntity> @this, int id)
        where TEntity : class, new() {
 
        var entity = @this.SingleOrDefault(x => ((IIdentified)x).ID == id);
 
        if (entity == null) {
            entity = new TEntity();
            @this.InsertOnSubmit(entity);
        }
 
        if (entity is IAuditable)
            ((IAuditable) entity).ModifiedDate = DateTime.Now;
        return entity;
    }
 
    public static TEntity Upsert<TEntity>(this Table<TEntity> @this, Func<TEntity, bool> predicate)
        where TEntity : class, new() {
 
        var entity = @this.SingleOrDefault(predicate);
 
        if (entity == null) {
            entity = new TEntity();
            @this.InsertOnSubmit(entity);
        }
 
        if (entity is IAuditable)
            ((IAuditable) entity).ModifiedDate = DateTime.Now;
 
        return entity;
    }
 
    public static void Delete<TEntity>(this Table<TEntity> @this, TEntity entity)
        where TEntity : class {
        @this.DeleteOnSubmit(entity);
    }
 
    public static void DeleteById<TEntity>(this Table<TEntity> @this, int id)
        where TEntity : class {
        var entity = @this.GetById(id);
        @this.Delete(entity);
    }
 
    public static void DeleteAll<TEntity>(this Table<TEntity> @this, 
        IEnumerable<TEntity> entities) where TEntity : class {
        entities.ToList().ForEach(e => @this.Delete(e));
    }
 
    public static void CommitChanges(this DataContext @this, RepositoryContext context) {
 
        var changeSet = @this.GetChangeSet();
 
        for (var i = 0; i < changeSet.Inserts.Count; i++)
            if (changeSet.Inserts[i] is IAuditable) UpdateAuditProperties((IAuditable) changeSet.Inserts[i], context, true);
 
        for (var i = 0; i < changeSet.Updates.Count; i++)
            if (changeSet.Updates[i] is IAuditable) UpdateAuditProperties((IAuditable) changeSet.Updates[i], context, false);
 
        @this.SubmitChanges();
    }
 
    private static void UpdateAuditProperties<TEntity>(TEntity entity, RepositoryContext context, 
        bool isInsert) where TEntity : IAuditable {
 
        entity.ModifiedDate = DateTime.Now;
        entity.ModifiedBy = context.UserID;
 
        if (isInsert) {
            entity.CreatedDate = DateTime.Now;
            entity.CreatedBy = context.UserID;
        }
    }
}

Here’s a rundown of the methods it creates and how to use them:

GetById
Simple enough, allows you to avoid using the LINQ syntax to find an element by it’s identifier.

var thing = DataContext.Things.GetById(3);

Upsert
This is probably the most convenient addition. It simply checks to see whether the object exists (by it’s ID) if so it returns it if not it creates a new one and returns that. It just avoids a lot of query-for-an-object-and-if-its-null-create-a-new-one-and-insert-it code.

var thing = DataContext.Things.Upsert(2);
var anotherThing = DataContext.Things.Upsert(x => x.Name == "That Thing");

Delete and DeleteById
Pretty obvious…

DataContext.Things.Delete(thing);
DataContext.Things.DeleteById(3);

CommitChanges
This is just a replacement for SubmitChanges that allows us to update some audit columns automagically.

Some of this magic is provided via a set of partial classes on those that LINQ-to-SQL generates that applies some additional interfaces to those classes. So, I have a Partials.cs file that contains the following:

public interface IAuditable {
    public DateTime CreatedDate { get; set; }
    public DateTime UpdatedDate { get; set; }
    public int CreatedBy { get; set; }
    public int UpdatedBy { get; set; }
}
 
public interface IIdentified {
    public int ID { get; set; }
}
 
partial class Thing : IIdentified, IAuditable { }
public class OtherThing : IIdentified { }
public class LastThing : IAuditable { }

As long as your generated class (and it’s related table) have properties of the same names as the IAuditable and IIdentified interfaces the LINQ-to-SQL extension methods can update them appropriately and in the background. I could have accomplished much of this by just inheriting a new DataContext and adding some additional functionality, but doing it this way you can add these methods to even a closed assembly.

Comment » | .NET

Scriptability via the DLR and PostSharp

December 15th, 2009 — 1:28pm

Making an application scriptable (particularly in a static language) has historically been difficult. With the advent of the DLR (Dynamic Language Runtime) on the .NET platform it becomes almost trivial to add scripting support to any application. For a recent project I needed the ability to add scripting hooks throughout the application and coupling the DLR with PostSharp AOP attributes made this effort pretty straightforward. Here’s how it was done:

Bring in the DLR
The DLR is implemented as a set of plain old DLL’s on top of the .NET Framework version 3.5…contrary to popular belief, you do not have to be running VS2010 or .NET 4 to use it.  Simply go to the CodePlex page for the DLR (http://dlr.codeplex.com), grab the source, and compile it.  There are solution files for VS2008 and VS2010.

For my purposes I used IronPython as the scripting language but IronRuby is available in the download as well.  To use the DLR your project will need to reference the following assemblies:

  • Microsoft.Dynamic.dll
  • Microsoft.Scripting.Core.dll
  • Microsoft.Scripting.dll

In order to use IronPython, you will also need to reference these:

  • IronPython.dll
  • IronPython.Modules.dll
  • Microsoft.Scripting.ExtensionAttribute.dll

Register the Languages
Now that your project contains all the references it needs it’s time to set up the little bit of code needed to host the DLR.  First you’ll need to add a few things to your configuration file (App.config or Web.config depending on your project type…this works equally well with either).  Just add the following to the node of your config file:

<section name="microsoft.scripting" 
	type="Microsoft.Scripting.Hosting.Configuration.Section, Microsoft.Scripting, 
		Version=0.9.6.20, Culture=neutral, PublicKeyToken=null" 
	requirePermission="false" />

And add the following somewhere in the root node:

<microsoft.scripting>
    <languages>
        <language names="IronPython;Python;py" 
			extensions=".py" displayName="IronPython 2.6" 
			type="IronPython.Runtime.PythonContext, IronPython, 
				Version=2.6.10920.0, Culture=neutral, PublicKeyToken=null" />
    </languages>
</microsoft.scripting>

This basically just tells the runtime what languages to register, what versions of those languages, and some info about file extensions, etc.

Bring in PostSharp
Now I’m going to skip over to PostSharp for a minute. If you’re not familiar with PostSharp you should run over to the website (http://www.postsharp.org) and check it out before continuing. In short, it’s an AOP (Aspect Oriented Programming) library for .NET that allows easy injection of cross-cutting concerns through an application primarily via attributes. The 10sec. version of how it works is this…it injects code in a post-compilation step. This sounds scary, but it’s not. In fact, this allows it to be extremely fast at runtime even if a small penalty is paid at compile-time.

In order to get PostSharp setup, it’s a simple download and install. If you don’t want to install it on your machine, you can simply bin deploy it but it does require a tweak to your .proj file to get the post-compilation to work. If you want to go that route, you can find instructions here. At this point you will either follow those instructions or if you’ve installed directly, just include the following assemblies in your project:

  • PostSharp.Laos.dll
  • PostSharp.Public.dll

The ScriptHook Attribute
PostSharp’s primary way of injecting an aspect is through attributes. In our case we’re going to inherit from the OnMethodBoundaryAspect attribute in the PostSharp.Laos namespace. This attribute exposes four virtual methods that allow injection of code at different points in the execution of a method:

  • OnEntry
  • OnExit
  • OnException
  • OnSuccess

Each of these methods accept a MethodExecutionEventArgs as an argument that provides access (including arguments) to the method on which the attribute is applied. Here’s the ScriptHook attribute:

[Serializable]
public class ScriptHookAttribute : OnMethodBoundaryAspect {
 
    private readonly ScriptEventTypes _scriptEventType;
 
    public ScriptEventAttribute(ScriptEventTypes scriptEventType) {
        _scriptEventType = scriptEventType;
    }
 
    public override void OnSuccess(MethodExecutionEventArgs eventArgs) {
        var args = eventArgs.GetReadOnlyArgumentArray();
        ScriptRunner.ExecuteEvent(_scriptEventType, args);
    }
}

For now, ignore the ScriptRunner reference, but notice that I’ve created an enum (ScriptEventTypes) that distinguishes different “types” of scripts to be run, by types, I mean different usages. At some point you can switch on the type of event that’s occurring and change which scripts are loaded or how they are handled, etc. You would use the attribute like this:

[ScriptHook(ScriptEventTypes.ThingSaved)]
public void SaveThing(Thing t) { }
 
[ScriptHook(ScriptEventTypes.ThingDeleted)]
public void DeleteThing(Thing t) { }

The ScriptRunner
At this point we’ve got the DLR available and we have the PostSharp attribute that’s injecting our hooks. Now we need something to execute our scripts. Enter the ScriptRunner:

public static class ScriptRunner {
 
    public static void ExecuteEvent(ScriptEventTypes scriptEventType, object[] eventContext) {
 
    IScriptEvent scriptEvent = null;
 
    switch (scriptEventType) {
        case ScriptEventTypes.ThingSaved:
	    scriptEvent = new ThingSavedScriptEvent();
	    break;
	case ScriptEventTypes.ThingDeleted:
	    scriptEvent = new ThingDeletedScriptEvent;
	    break;
	}
 
	if (scriptEvent != null) {
            try {
                scriptEvent.ExecuteScripts(eventContext);
            } catch {
                throw new ScriptExecutionExecption();
            }
        }
    }
}

The runner is just a simple static class with a single ExecuteEvent method…it’s so simple in fact that it speaks for itself. The one thing to note is the eventContext argument to the ExecuteEvent method. If you look back at the ScriptHook attribute you can see that is’ passing an object array from the MethodExecutionEventArgs instance. This object array contains all the arguments to the executing method. So for SaveThing() or DeleteThing() for example the eventContext array contains the instance of Thing that was passed.

The IScriptEvent Classes
Lastly, we need something to actually grab the scripts and execute them in something like the context of the executing method. So that’s where our example ThingSavedScriptEvent and ThingDeletedScriptEvent come in to play. Here’s an example:

public class ThingSavedScriptEvent : IScriptEvent {
 
    private readonly ScriptRuntime _scriptRuntime;
    private readonly IThingRepository _thingRepo;
 
    public ProjectCreatedScriptEvent() {
        _thingRepo = new ThingRepository();
        _scriptRuntime = ScriptRuntime.CreateFromConfiguration();
    }
 
    public void ExecuteScripts(object[] contextValues) {
 
        var thing = (Thing)contextValues.SingleOrDefault(x => x is Thing);
 
		var scriptScope = _scriptRuntime.CreateScope();
        scriptScope.SetVariable("thingRepo", _thingRepo);
        scriptScope.SetVariable("thing", thing);
 
        var script = GetScript();
 
        var scriptEngine = _scriptRuntime.GetEngine("IronPython");
        scriptEngine.Execute(script, scriptScope);
    }
 
    private static string GetScript() {
 
        var script = string.Empty;
        try {
            code = File.ReadAllText(HttpContext.Current.Server.MapPath(
                ConfigSettings.ProjectTemplateStorePath + "ThingSaved.py"));
        } catch (FileNotFoundException) { }
        return script;
    }
}

The ScriptRuntime class is where the DLR magic happens. We create an instance of it and then ask it for an engine that can execute IronPython scripts. ScriptScope is a class used for constructing the scope that the script will have available to it. Here for example we’re “passing” the ‘thingRepo’ and ‘thing’ variables into the script via the ScriptScope. Lastly, we get the engine from the ScriptRuntime, pass it both the ScriptScope and the literal text of the script. And that’s that…pretty easy eh?

2 comments » | .NET, IronPython

The Reusable Test Scope Pattern

August 22nd, 2009 — 4:31pm

I’m almost positive someone has come up with this pattern before, but it was a big help on a recent project so I thought I’d share it. The basic goal of the pattern is to make the setup and teardown work of test classes reusable. We found this to cause particular pain for integration tests written against a database where multiple dependent records needed to be created before the actual test could be performed. The second goal was that these externalized bits of code would be composable so that tests requiring multiple setup resources could create just the right ones.

Before really describing the workflow of the pattern, here are the basic components of our implementation:

The ITestScope Interface

public interface ITestScope {
    void SetupScope();
    void TeardownScope();
}

These two methods (SetupScope and TeardownScope) are where we move all of the test setup and cleanup logic. Any state that needs to be maintained between setup and teardown (i.e. needs to be available to the test itself) can be stored in public properties or fields on the class implementing ITestScope.

The TestBase Class

public abstract class TestBase {
    public abstract void ScopeSetup();
    public abstract void ScopeTeardown();
}

Requiring that all test classes inherit from TestBase insures that at least developers will be considering the setup and cleanup of their tests. The TestBase.ScopeSetup method would include instantiating and calling the TestScope.SetupScope methods for any scopes that are needed in the test class…likewise for teardown.

The TestFactory Class

public static class TestFactory {
    public static TRepository GetRepository<TRepository>() where TRepository : class {}
    public static TController GetController<TController>() where TController : Controller {}
}

This isn’t directly related to the scope pattern, but it’s invaluable as a point where you can use a DI container in the test project. All repository or controllers (or other resources) can be obtained from here.

An Example

So here’s an example test class that might use this pattern.

public class CustomerTestScope : ITestScope {
 
	private ICustomerRepository _customerRepo;
 
	public Customer TestCustomer { get; private set; }
 
	public CustomerTestScope(ICustomerRepository customerRepo) {
		_customerRepo = customerRepo;
	}
 
	public void SetupScope() {
		TestCustomer = _customerRepo.Create();
                // Other complex test setup logic
	}
 
	public void TeardownScope() {
		_customerRepo.Delete(TestCustomer);
                // The reverse of the complex setup logic
	}
}
 
[TestClass]
public class CustomerRepositoryTests : TestBase {
 
	private CustomerTestScope _customerScope;
	private ICustomerRepository _customerRepo;
 
	[TestInitialize]
	public override void ScopeSetup() {
 
		_customerRepo = TestFactory.GetRepository<ICustomerRepository>();
 
		_customerScope = new CustomerTestScope(_customerRepo);
		_customerScope.SetupScope();
	}
 
	[TestMethod]
	public void SaveCustomer_ShouldRenameCustomer_WhenNewNameGiven() {
 
		_customerScope.TestCustomer.Name = "New Name";
		_customerRepo.Save(_customerScope.TestCustomer);
 
		var customer = _customerRepo.GetById(_customerScope.TestCustomer.ID);
 
		Assert.IsTrue(customer.Name == "New Name", 
			"SaveCustomer failed to update customer name.");
	}
 
	[TestCleanup]
	public override void ScopeTeardown() {
		_customerScope.TeardownScope();
	}
}

If additional scopes were needed then they could simple be added to the test class. For example, if a ProductTestScope was also needed then it would be initialized just like the CustomerTestScope and torn down appropriately.

Dependencies between scopes would be supplied via constructor parameters. This does imply that the SetupScope methods be called in sequence and TeardownScope be called in reverse order.

That’s the pattern, pretty simple but now the logic for creating these setup conditions can be offloaded and reused in a simple way and we’ve made it easy to clean up that test data after the tests complete.

Comment » | .NET, General Programming

Thoughts on Legacy Code

August 1st, 2009 — 11:57am

I was listening to a recent Hanselminutes podcast episode in which Scott spoke with Michael Feathers of ObjectMentor on the topic of legacy code. They touched on a number of really effective techniques for approaching a legacy code-base and I wanted to echo some of their thoughts and add a few of my own.

First, the definition of legacy code (as was discussed in the show) is really much broader than the face value of the phrase itself. In reality it’s harder to define “legacy code” than it is to know it when you see it. Generally I know I’m dealing with legacy code when any of the following happen:

1. I’m fearful of making changes…the magic box where this code lives might get angry
2. I’m confused and lost after more than a few clicks of “Go to Definition”
3. I find few or no tests, or the quality of tests is poor
4. There are tests but I get mixed or untrustworthy results from running them
5. I have to write more lines of setup code to write a test than the lines of code that I want to test
6. I can find nobody who will admit to understanding (or writing) this code

Doubtless there are more than just these indicators to tell you that you’re dealing with legacy code, but these are red-flags for sure. So what are some high-level techniques that we can use to help bring legacy code into the present…well, here are a few:

Under The Rug
We’ve all done it at home…there’s that last little bit of something that you missed with the broom. So, you lift up the corner of the living room rug and kick it under there with your foot. While you’re mom probably wouldn’t approve, sometimes with code this can be an acceptable approach – provided you acknowledge what you’re doing. What I mean by sweeping it under the rug is that you wrap a section of “legacy code” in a bit of more modern, testable code. In this way you can continue to use the legacy sections but a cleaner interface can be created for testing.

The Strangler Application
This is a pattern that was introduced to me in the Hanselminutes podcast. The basic premise is an extension of the “Under the Rug” concept. But instead of just wrapping the code in a clean exterior, we actually take that opportunity to slowly “choke” out the old code.

One technique that can be employed to slowly transform the codebase is using the “I” in SOLID…Interface Segregation. Applying one or more interfaces to each legacy class allows you to incrementally swap out implementations with clean, new code.

Embrace Continual Design
One thought that came out in the Hanselminutes episode was that design is never over. And I think this is really important to realize. Even in production/support mode, design is something that needs to be addressed. When extending an application, are we setting it up for continued success and maintainability. When re-factoring something or transforming it to un-legacy code, are we taking care that it won’t regress back into legacy code. If a team is focused on continual design it’s less likely for code to become legacy in the first place.

Posthumous Test Coverage
A third approach to legacy code is by testing the crap out of it. One could even make the argument that this is a pre-requisite to the previous three approaches. Spending a lot of time covering legacy code in tests has two major benefits. First, it helps the developer really understand the intent of the code, it’s style, quirks, and patterns. Secondly, it does what tests do best…helps make code changes safer. If a codebase is adequately covered in tests then making changes becomes less scary and potential regression is reduced.

Forklift
Lastly, there is one approach to legacy code that is rarely mentioned…and rightly so, it’s the most aggressive and can be the most costly. Start over…sometimes it’s the best approach.

Once in my life I had to deal with a massive codebase that had been through many, many, cycles of attempted refactorings. The years had left this poor application in a state of wild and unruly disrepair. After discussing all the options with the client it became evident that actually starting over was the best (and in this case, cheapest) way to accomplish the goal.

Starting over can be daunting but if you attack it in a practical and measured way, and possibly mix it with the other techniques described here it can be liberating. It’s important to have a good requirements phase when starting over, you can’t just look at legacy code and start re-writing it. And you need to have all the trappings of a well staffed project in place as well, including strong QA.

~~

So those are some thoughts on legacy code. As always, I’d appreciate hearing what everyone else thinks…what other techniques have you used?

Comment » | General Programming

Martin: A Lightweight REST Framework Inspired by Sinatra and MVC

July 2nd, 2009 — 1:29pm

Those of you who work in the Rails community will know something about Sinatra. It’s a lightweight web framework for building REST services in Ruby. I’m not a Ruby programmer (I know enough to be dangerous) but I really like the idea of Sinatra.

The basic premise of Sinatra is that every method defines a route, and a route (URL + Verb) invokes a single method. Ruby, being a dynamic language, makes this extremely easy. Like I said I’m no Ruby guru but the built-in introspection of the language makes it easier to build a DSL that can describe these route methods.

Martin, isn’t at all a port of Sinatra. Rather it’s inspired by the lightweight nature of Sinatra and it’s general pattern. Martin relies on the new System.Web.Routing namespace in .NET 3.5 SP1 and will eventually ship with a DSL that will encapsulate defining your RouteTable. Because it doesn’t have any external dependencies and really doesn’t need any templates it can plug into any ASP.NET or ASP.NET MVC project and live along side just fine*.

In Martin, the first step to creating a new action is defining it with the Config API. At this point it’s excruciatingly simple.

In Global.asax:

protected void Application_Start(object sender, EventArgs e) {
    Config.AddAction<DateAction>("Show/CurrentDate");
}

What this does is maps the URL: http://domain.com/Show/CurrentDate to the DateAction.Get() method. Behind the scenes there is a factory class that’s constructing instances of DateAction, calling the Get() method, and then responding with the result to the request pipeline.

The DateAction class looks like this:

public class DateAction : ActionBase {
    public override IResponse Get() {
        return Text(string.Format("The current date is {0}.", DateTime.Now));
    }
}

There are four methods on ActionBase that can be overridden: Get(), Post(), Put(), and Delete(). Each method (obviously) corresponds to an HTTP Verb and only requests using that verb will invoke the appropriate method. So, the same URL pattern can be mapped to two different methods provided that the request uses different verbs.

The ActionBase class provides a way to get access to any RouteData or Request items that might be part of the request. Using the ValueOf(String key) method you can find for example an ID that’s tacked on to the URL or a piece of data from the querystring or form collection.

~~

For now the project is in a very early stage, no real sample, no real documentation. However, it’s so simple that reading through the handful of classes that make up the project is almost all the documentation you’ll need. If you get a chance, poke around and let me know what you think…if you want to help make it better just let me know and I’ll get you commit rights to the Google Code repo.

Source at: http://code.google.com/p/martin-framework/

* In the case of integration with ASP.NET MVC the routes defined by Martin and those your application might need could conflict so care must be taken to insure there are no collisions.

1 comment » | .NET, Martin

jQuery Plugin: tablePager

June 5th, 2009 — 10:38am

Here’s a quick table pager plugin for jQuery. Unlike a number of the other plugins out there, this one doesn’t alter the DOM tree of the table itself it just hides the rows that are not available for the current page. In this way you can still get access to all the elements of the table throughout the lifetime of the page.

Plugin Source:

(function($) {
    $.fn.tablePager = function(options) {
 
        /*
        options = {
        firstBtn: Element,
        prevBtn: Element,
        nextBtn: Element,
        lastBtn: Element,
        indicator: Element,
        sizeSelect: Element,
        pageSize: Number,
        onPageChange: Callback,
        onPageSizeChange: Callback
        };
        */
 
        var tbl = this;
        var defaults = { pageSize: 10 };
        var opts = $.extend(defaults, options);
 
        var pageIndex = 0;
        var pageSize = opts.pageSize;
        var rowCount = getRows().length;
        var pageCount = getPageCount();
 
        function init() {
            bindControlHandlers();
            updatePaging();
            updateIndicator();
        }
 
        function getRows() {
            return tbl.find("tbody tr");
        }
 
        function bindControlHandlers() {
            if (opts.firstBtn) { 
	        opts.firstBtn.unbind().bind("click", 
		    function() { changePage(0); }); 
            }
            if (opts.prevBtn) { 
		opts.prevBtn.unbind().bind("click", 
			function() { changePage(pageIndex - 1); }); 
	    }
            if (opts.nextBtn) { 
		opts.nextBtn.unbind().bind("click", 
			function() { changePage(pageIndex + 1); }); 
	    }
            if (opts.lastBtn) { 
		opts.lastBtn.unbind().bind("click", 
			function() { changePage(pageCount - 1); }); 
	    }
            if (opts.sizeSelect) { 
		opts.sizeSelect.unbind().bind("change", 
                    function() { changePageSize(parseInt($(this).val())); }); 
	    }
        }
 
        function getPageCount() {
            return (rowCount % pageSize) ? 
		Math.floor(rowCount / pageSize) + 1 : (rowCount / pageSize);
        }
 
        function changePage(toIndex) {
            if (toIndex >= 0 && toIndex <= (pageCount - 1)) {
                pageIndex = toIndex;
                updatePaging();
                updateIndicator();
                if (opts.onPageChange) {
                    opts.onPageChange.call(pageIndex);
                }
            }
        }
 
        function changePageSize(toSize) {
            pageSize = toSize;
            pageCount = getPageCount();
            updatePaging();
            updateIndicator();
            if (opts.onPageSizeChange) {
                opts.onPageSizeChange.call(pageSize);
            }
        }
 
        function updateIndicator() {
	    if (opts.indicator) {
            	opts.indicator.val((pageIndex + 1) + "/" + pageCount);
	    }
        }
 
        function updatePaging() {
            var start = pageIndex * pageSize;
            var end = start + pageSize;
            var i = 0;
            var rows = getRows();
            rows.each(function() {
                if (i >= start && i < end) {
                    $(rows[i]).show();
                } else {
                    $(rows[i]).hide();
                }
                i++;
            });
        }
 
        init();
 
        return {
            changePage: function(moveTo) { changePage(moveTo - 1); },
            getPageSize: function() { return pageSize; },
            getPageIndex: function() { return pageIndex; }
        };
    };
})(jQuery);

Usage:

var pager = $("#testTable").tablePager({
    firstBtn: $(".first"), // The element that pages to the first page
    prevBtn: $(".prev"), // The element that pages to the previous page
    nextBtn: $(".next"), // The element that pages to the next page
    lastBtn: $(".last"), // The element that pages to the last page
    indicator: $(".curpage"), // The input element that stores the current page (i.e. "1/5")
    sizeSelect: $(".pagesize"), // The select element that indicates the current page size
    pageSize: 5, // The initial page size
    onPageChange: function(newIndex) { alert(newIndex); }, // Page change callback
    onPageSizeChange: function(newSize) { alert(newSize); } // Page size change callback
});
 
pager.changePage(2); // Causes the page to change from outside the plugin
var pageIndex = pager.getPageIndex(); // Returns the current page index
var pageSize = pager.getPageSize(); // Returns the current page size

And here’s some example markup for the pager elements:

<span class="first">First</span>&nbsp;&nbsp;
<span class="prev">Prev</span>&nbsp;&nbsp;
<span class="next">Next</span>&nbsp;&nbsp;
<span class="last">Last</span>
<br><br>
<input type="text" class="curpage" />&nbsp;&nbsp;
<select class="pagesize">
    <option value="5">5</option>
    <option value="10">10</option>
    <option value="20">20</option>
</select>

As you can see it’s really simple and flexible. You can supply any elements you want to the pager. If you don’t supply a first and last button elements for example then those will be ignored, if you don’t specify an indicator then that will be ignored too. The most important thing is that your DOM stays intact, this allows you to continue to manipulate the “hidden” portions of the table (i.e. the stuff not on the current page) without having to access some crazy expando property that other pager plugins use to stash the un-shown portions of the table.

As always, I’d appreciate any feedback.

1 comment » | JavaScript, jQuery

Back to top