Saving State in Javascript and AJAX

We're doing something cool in work, to do with resource scheduling in a nice ajax-y, jQuery-UI-ey way.  There are a set number of filters to apply to get the view of the resources you want, which can include Department (for example, Development, Front-End, Client Services, Marketing), the Employee themselves, Employee Roles to show Senior Developers etc, Client and Project. The problem was, these are all filtered via AJAX, and refreshing the page would cause you to lose your filters.

One way that developers solved this was by keeping the state in the "hash" portion of the URL.  The most prevalent method of this was called the "Hashbang" which would add the hash (octothorp) and an excalamation point (! - the "bang").  This is easy but could lead to messy / long URL.

Another method that is pretty convenient is to set a cookie containing the parameters. The downside of this is you can't send the same view that you're looking at to a coworker. That cookie is on your computer only.

I couldn't decide which one to implement so I decided to do both.

    var serializers = {};

    serializers.hash = function(){
        this.serialize = function(str){
            window.location.hash = str;
        };         this.prepDeserialize = function(){
            var hash = window.location.hash;
            return hash == null || hash.indexOf("#") != 0 ? "" : hash.substring(1);
        };
    }     serializers.cookie = function(){
        this.serialize = function(str){
            window.Cookie.set("ajaxState_serialize", str);
        };         this.prepDeserialize = function(){
            return window.Cookie.get("ajaxState_serialize");
        };
    }

I have a cookie helper class which wraps what you can find on quirksmode.org.  If you want to use this code, you can provide your own cookie serializer, your own custom serializers, or whatever, without changing the rest of the code.

The rest of the code is just adding and subtracting values from the serialized data. To call it, it's simply this:

                var data = $.fn.projectFilter.getFilterData($wrap);
                window.AjaxState.serialize(data, true);

So you get the filter object and serialize it. The true on the call to serialize just says to "commit it", which means to write the data to the cookie or the hash on the same call. I've put this code up on this site, you can download it.

This one is a C# post

At work, I was working on cool stuff, but then my boss was like "I need this report and this report and this report. Thanks."

I'm not one to turn down such a politely worded and completely fictitious request. Reports are easy until the requests become stuff like "Include subtotal line for every Apple category and Orange category"

My data set was obviously not Apples and Oranges, but here's what I did to quickly and easily make subtotals for each of these

First, I made some C# Attributes, which are nice when you like to work in the meta.

public class MyReportItem { [SubtotalGroup(GroupName = "Fruit Type")] public string FruitType { get; set; } public string FruitName { get; set; } [SubtotalSum] public int Count { get; set; } [SubtotalAverage] public int SalesPerDay { get; set; } [SubtotalSummaryDesignator] public bool IsSubtotalLine { get; set; } [TotalDesignator] public bool IsTotalLine { get; set; } }

Your SQL might look like this:

select FruitType, FruitName, StockQty, SalesPerDay from Fruits order by FruitType, FruitName

So your data looks like this

'Apple', 'Mcintosh', 12, 80 'Apple', 'Delicious Red', 22, 50 'Orange', 'Some Orange Name', 33, 90

The code I wrote allows that data to be quickly, easily, and automatically shown like this:

'Apple', 'Mcintosh', 12, 80 'Apple', 'Delicious Red', 22, 50 'Apple Subtotal', '', 34, 65 'Orange', 'Some Orange Name', 33, 90 'Orange Subtotal', '', 33, 90

Notice the "SalesPerDay" column has an average attribute on it, not a sum. Here's the meat of my code, after getting the attributes and the data all figured out.

public List<T> PopulateSubtotalItems() { List<T> withSubs = new List<T>(); if (this.list.Count == 0) return withSubs; // allow multiple group by with subtotals. e.g. group by Fruit Name and say fruit type, like "Citrus" // to subtotal Oranges and subtotal Limes and then subtotal Citrus List<GroupSub<T>> subs = new List<GroupSub<T>>(); foreach (string key in this.groupBy.Keys) { T sub = new T(); GroupSub<T> groupSub = this.groupBy[key]; groupSub.SubRecord = sub; // sets the properties which designate the group. So this subgroup might set FruitType to "Apple" this.SetGroup(groupSub, this.list[0]); // sets the bool property which the subtotal designator is on to true. this.SetSummary(groupSub); subs.Add(groupSub); } // if there's a bool property with the "TotalDesignator" attribute, include total GroupSub<T> totals = null; if (this.includeTotal) { T sub = new T(); totals = new GroupSub<T>(); totals.SubRecord = sub; totals.IsTotal = true; this.SetTotal(totals); // sets the property which the TotalDesignator is on to true } subs = subs.OrderBy(grp => grp.Sequence).ToList(); int grpCount = 0; for (int i = 0; i < this.list.Count; i++) { bool added = false, last = i == this.list.Count - 1; foreach (GroupSub<T> grp in subs) { bool same = SameGroup(grp, this.list[i]); if (!same) { this.Average(grp, grpCount); // set the average properties to the sum / grpCount withSubs.Add(grp.SubRecord); // add the subtotal record to the group grpCount = 0; // start afresh grp.SubRecord = new T(); this.SetSummary(grp); SetGroup(grp, this.list[i]); } Increment(grp, this.list[i]); if (last) // special handling on the last one. { this.Average(grp, grpCount); if (!added) { Increment(totals, this.list[i]); withSubs.Add(this.list[i]); added = true; } withSubs.Add(grp.SubRecord); grpCount++; } } if (!added) { Increment(totals, this.list[i]); withSubs.Add(this.list[i]); added = true; grpCount++; } } // add the total line if (this.includeTotal) { this.Average(totals, this.list.Count); // average the total record withSubs.Add(totals.SubRecord); } return withSubs; // that's it!! }

As you can see, I no longer have to dread doing subtotals on reports!